diff --git a/core/app/views/digikamview.h b/core/app/views/digikamview.h index efbcd8c627..4e158245f8 100644 --- a/core/app/views/digikamview.h +++ b/core/app/views/digikamview.h @@ -1,343 +1,343 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2002-16-10 * Description : implementation of album view interface. * * Copyright (C) 2002-2005 by Renchi Raju * Copyright (C) 2002-2018 by Gilles Caulier * Copyright (C) 2009-2011 by Johannes Wienke * Copyright (C) 2010-2011 by Andi Clemens * * 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. * * ============================================================ */ #ifndef DIGIKAM_VIEW_H #define DIGIKAM_VIEW_H // Qt includes #include #include // Local includes #include "applicationsettings.h" #include "metaengine_rotation.h" #include "digikam_config.h" #include "searchtextbar.h" #include "imageinfo.h" #include "digikammodelcollection.h" #include "sidebarwidget.h" #include "stackedview.h" #include "dlayoutbox.h" namespace Digikam { class AlbumIconItem; class Album; class PAlbum; class TAlbum; class BatchSyncMetadata; class FilterStatusBar; class SlideShowSettings; class DCategorizedView; class ImageFilterModel; class DigikamView : public DHBox { Q_OBJECT public: explicit DigikamView(QWidget* const parent, DigikamModelCollection* const modelCollection); ~DigikamView(); void applySettings(); void refreshView(); void clearHistory(); void getForwardHistory(QStringList& titles); void getBackwardHistory(QStringList& titles); void showSideBars(); void hideSideBars(); void toggleLeftSidebar(); void toggleRightSidebar(); void previousLeftSideBarTab(); void nextLeftSideBarTab(); void previousRightSideBarTab(); void nextRightSideBarTab(); void setToolsIconView(DCategorizedView* const view); void setThumbSize(int size); void toggleShowBar(bool); void setRecurseAlbums(bool recursive); void setRecurseTags(bool recursive); void imageTransform(MetaEngineRotation::TransformationAction transform); void connectIconViewFilter(FilterStatusBar* const filter); QUrl currentUrl() const; bool hasCurrentItem() const; ImageInfo currentInfo() const; Album* currentAlbum() const; /** * Get currently selected items. By default only the first images in groups are * given, while all can be obtained by setting the grouping parameter to true. * Given an operation, it will be determined from settings/user query whether * only the first or all items in a group are returned. * Ideally only the latter (giving an operation) is used. */ QList selectedUrls(bool grouping = false) const; QList selectedUrls(const ApplicationSettings::OperationType type) const; ImageInfoList selectedInfoList(const bool currentFirst = false, const bool grouping = false) const; ImageInfoList selectedInfoList(const ApplicationSettings::OperationType type, const bool currentFirst = false) const; /** * Get all items in the current view. * Whether only the first or all grouped items are returned is determined * as described above. */ QList allUrls(bool grouping = false) const; ImageInfoList allInfo(const bool grouping = false) const; ImageInfoList allInfo(const ApplicationSettings::OperationType type) const; /** * Query whether the operation to be performed on currently selected or all * all items in the currently active view should be performed on all * grouped items or just the first. */ bool allNeedGroupResolving(const ApplicationSettings::OperationType type) const; bool selectedNeedGroupResolving(const ApplicationSettings::OperationType type) const; double zoomMin() const; double zoomMax() const; void presentation(); void toggleTag(int tagID); void toggleFullScreen(bool set); QList leftSidebarWidgets() const; StackedView::StackedViewMode viewMode() const; Q_SIGNALS: void signalAlbumSelected(Album*); void signalImageSelected(const ImageInfoList& selectedImage, const ImageInfoList& allImages); void signalNoCurrentItem(); void signalSelectionChanged(int numberOfSelectedItems); void signalThumbSizeChanged(int); void signalZoomChanged(double); void signalSwitchedToPreview(); void signalSwitchedToIconView(); void signalSwitchedToMapView(); void signalSwitchedToTableView(); void signalSwitchedToTrashView(); void signalGotoAlbumAndItem(const ImageInfo&); void signalGotoDateAndItem(AlbumIconItem*); void signalGotoTagAndItem(int tagID); void signalChangedTab(QWidget*); void signalFuzzySidebarActive(bool active); public Q_SLOTS: void setZoomFactor(double zoom); // View Action slots void slotZoomIn(); void slotZoomOut(); void slotZoomTo100Percents(); void slotFitToWindow(); void slotSlideShowAll(); void slotSlideShowSelection(); void slotSlideShowRecursive(); void slotSlideShowManualFromCurrent(); void slotSlideShowManualFrom(const ImageInfo& info); // Album action slots void slotRefresh(); void slotNewAlbum(); void slotSortAlbums(int role); void slotDeleteAlbum(); void slotRenameAlbum(); void slotAlbumPropsEdit(); void slotAlbumOpenInFileManager(); void slotAlbumHistoryBack(int steps=1); void slotAlbumHistoryForward(int steps=1); void slotAlbumWriteMetadata(); void slotAlbumReadMetadata(); void slotAlbumSelected(const QList& albums); void slotGotoAlbumAndItem(const ImageInfo& imageInfo); void slotGotoDateAndItem(const ImageInfo& imageInfo); void slotGotoTagAndItem(int tagID); void slotSelectAlbum(const QUrl& url); void slotSetCurrentWhenAvailable(const qlonglong id); void slotSetAsAlbumThumbnail(const ImageInfo& info); // Tag action slots void slotNewTag(); void slotDeleteTag(); void slotEditTag(); void slotOpenTagsManager(); void slotAssignTag(); // Search action slots void slotNewKeywordSearch(); void slotNewAdvancedSearch(); - void slotNewDuplicatesSearch(PAlbum* album); + void slotNewDuplicatesSearch(PAlbum* album = 0); void slotNewDuplicatesSearch(const QList& albums); void slotNewDuplicatesSearch(const QList& albums); // Image action slots void slotImageLightTable(); void slotImageAddToLightTable(); void slotImageAddToCurrentQueue(); void slotImageAddToNewQueue(); void slotImageAddToExistingQueue(int); void slotImagePreview(); void slotMapWidgetView(); void slotTableView(); void slotIconView(); void slotImageEdit(); void slotImageFindSimilar(); void slotImageScanForFaces(); void slotImageExifOrientation(int orientation); void slotImageRename(); void slotImageDelete(); void slotImageDeletePermanently(); void slotImageDeletePermanentlyDirectly(); void slotImageTrashDirectly(); void slotImageWriteMetadata(); void slotImageReadMetadata(); void slotSelectAll(); void slotSelectNone(); void slotSelectInvert(); void slotSortImages(int order); void slotSortImagesOrder(int order); void slotSeparateImages(int mode); void slotImageSeparationSortOrder(int order); void slotMoveSelectionToAlbum(); void slotImagePaste(); void slotAssignPickLabel(int pickId); void slotAssignColorLabel(int colorId); void slotAssignRating(int rating); void slotAssignTag(int tagID); void slotRemoveTag(int tagID); // Tools action slots. void slotEditor(); void slotLightTable(); void slotQueueMgr(); void slotFileWithDefaultApplication(); void slotLeftSideBarActivate(QWidget* widget); void slotLeftSideBarActivate(SidebarWidget* widget); void slotLeftSideBarActivateAlbums(); void slotLeftSideBarActivateTags(); void slotRightSideBarActivateTitles(); void slotRightSideBarActivateComments(); void slotRightSideBarActivateAssignedTags(); void slotFocusAndNextImage(); void slotCreateGroupFromSelection(); void slotCreateGroupByTimeFromSelection(); void slotCreateGroupByFilenameFromSelection(); void slotRemoveSelectedFromGroup(); void slotUngroupSelected(); private: void toggleZoomActions(); void setupConnections(); void loadViewState(); void saveViewState(); void changeAlbumFromHistory(QList album, QWidget* const widget); void slideShow(const ImageInfoList& infoList); private Q_SLOTS: void slotAllAlbumsLoaded(); void slotAlbumsCleared(); void slotImageSelected(); void slotTogglePreviewMode(const ImageInfo& info); void slotDispatchImageSelected(); void slotLeftSidebarChangedTab(QWidget* w); void slotFirstItem(); void slotPrevItem(); void slotNextItem(); void slotLastItem(); void slotSelectItemByUrl(const QUrl&); void slotAwayFromSelection(); void slotViewModeChanged(); void slotEscapePreview(); void slotSlideShowBuilderComplete(const SlideShowSettings& settings); void slotThumbSizeEffect(); void slotZoomFactorChanged(double); void slotSidebarTabTitleStyleChanged(); void slotImageChangeFailed(const QString& message, const QStringList& fileNames); void slotRatingChanged(const QUrl&, int); void slotColorLabelChanged(const QUrl&, int); void slotPickLabelChanged(const QUrl&, int); void slotToggleTag(const QUrl&, int); void slotPopupFiltersView(); void slotSetupMetadataFilters(int); void slotAlbumRefreshComplete(); void slotRefreshImagePreview(); void slotShowContextMenu(QContextMenuEvent* event, const QList& extraGroupingActions = QList()); void slotShowContextMenuOnInfo(QContextMenuEvent* event, const ImageInfo& info, const QList& extraGroupingActions = QList(), ImageFilterModel* imageFilterModel = 0); void slotShowGroupContextMenu(QContextMenuEvent* event, const QList& selectedInfos, ImageFilterModel* imageFilterModel = 0); private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_VIEW_H diff --git a/core/data/database/dbconfig.xml.cmake.in b/core/data/database/dbconfig.xml.cmake.in index 334aaa895e..4dff8c5698 100644 --- a/core/data/database/dbconfig.xml.cmake.in +++ b/core/data/database/dbconfig.xml.cmake.in @@ -1,2600 +1,2613 @@ QSQLITE ${DBCORECONFIG_XML_VERSION} TestHost DatabaseName UserName Password Port ConnectOptions CREATE TRIGGER privcheck_trigger DELETE ON PrivCheck BEGIN SELECT * FROM PrivCheck; END; DROP TRIGGER privcheck_trigger; CREATE TABLE PrivCheck (id INT, name VARCHAR(35)); ALTER TABLE PrivCheck ADD COLUMN addedColumn; DROP TABLE PrivCheck; DROP TABLE IF EXISTS PrivCheck; CREATE TABLE AlbumRoots (id INTEGER PRIMARY KEY, label TEXT, status INTEGER NOT NULL, type INTEGER NOT NULL, identifier TEXT, specificPath TEXT, UNIQUE(identifier, specificPath)); CREATE TABLE Albums (id INTEGER PRIMARY KEY, albumRoot INTEGER NOT NULL, relativePath TEXT NOT NULL, date DATE, caption TEXT, collection TEXT, icon INTEGER, UNIQUE(albumRoot, relativePath)); CREATE TABLE Images (id INTEGER PRIMARY KEY, album INTEGER, name TEXT NOT NULL, status INTEGER NOT NULL, category INTEGER NOT NULL, modificationDate DATETIME, fileSize INTEGER, uniqueHash TEXT, + manualOrder INTEGER, UNIQUE (album, name)); CREATE TABLE ImageInformation (imageid INTEGER PRIMARY KEY, rating INTEGER, creationDate DATETIME, digitizationDate DATETIME, orientation INTEGER, width INTEGER, height INTEGER, format TEXT, colorDepth INTEGER, colorModel INTEGER); CREATE TABLE ImageMetadata (imageid INTEGER PRIMARY KEY, make TEXT, model TEXT, lens TEXT, aperture REAL, focalLength REAL, focalLength35 REAL, exposureTime REAL, exposureProgram INTEGER, exposureMode INTEGER, sensitivity INTEGER, flash INTEGER, whiteBalance INTEGER, whiteBalanceColorTemperature INTEGER, meteringMode INTEGER, subjectDistance REAL, subjectDistanceCategory INTEGER); CREATE TABLE VideoMetadata (imageid INTEGER PRIMARY KEY, aspectRatio TEXT, audioBitRate TEXT, audioChannelType TEXT, audioCompressor TEXT, duration TEXT, frameRate TEXT, exposureProgram INTEGER, videoCodec TEXT); CREATE TABLE ImagePositions (imageid INTEGER PRIMARY KEY, latitude TEXT, latitudeNumber REAL, longitude TEXT, longitudeNumber REAL, altitude REAL, orientation REAL, tilt REAL, roll REAL, accuracy REAL, description TEXT); CREATE TABLE ImageComments (id INTEGER PRIMARY KEY, imageid INTEGER, type INTEGER, language TEXT, author TEXT, date DATETIME, comment TEXT, UNIQUE(imageid, type, language, author)); CREATE TABLE ImageCopyright (id INTEGER PRIMARY KEY, imageid INTEGER, property TEXT, value TEXT, extraValue TEXT, UNIQUE(imageid, property, value, extraValue)); CREATE TABLE IF NOT EXISTS Tags (id INTEGER PRIMARY KEY, pid INTEGER, name TEXT NOT NULL, icon INTEGER, iconkde TEXT, UNIQUE (name, pid)); CREATE TABLE IF NOT EXISTS TagsTree (id INTEGER NOT NULL, pid INTEGER NOT NULL, UNIQUE (id, pid)); CREATE TABLE IF NOT EXISTS ImageTags (imageid INTEGER NOT NULL, tagid INTEGER NOT NULL, UNIQUE (imageid, tagid)); CREATE TABLE IF NOT EXISTS ImageProperties (imageid INTEGER NOT NULL, property TEXT NOT NULL, value TEXT NOT NULL, UNIQUE (imageid, property)); CREATE TABLE IF NOT EXISTS Searches (id INTEGER PRIMARY KEY, type INTEGER, name TEXT NOT NULL, query TEXT NOT NULL); CREATE TABLE DownloadHistory (id INTEGER PRIMARY KEY, identifier TEXT, filename TEXT, filesize INTEGER, filedate DATETIME, UNIQUE(identifier, filename, filesize, filedate)); CREATE TABLE IF NOT EXISTS Settings (keyword TEXT NOT NULL UNIQUE, value TEXT); CREATE TABLE ImageHistory (imageid INTEGER PRIMARY KEY, uuid TEXT, history TEXT); CREATE TABLE ImageRelations (subject INTEGER, object INTEGER, type INTEGER, UNIQUE(subject, object, type)); CREATE TABLE TagProperties (tagid INTEGER, property TEXT, value TEXT); CREATE TABLE ImageTagProperties (imageid INTEGER, tagid INTEGER, property TEXT, value TEXT); CREATE INDEX dir_index ON Images (album); CREATE INDEX hash_index ON Images (uniqueHash); CREATE INDEX tag_index ON ImageTags (tagid); CREATE INDEX tag_id_index ON ImageTags (imageid); CREATE INDEX image_name_index ON Images (name); CREATE INDEX creationdate_index ON ImageInformation (creationDate); CREATE INDEX comments_imageid_index ON ImageComments (imageid); CREATE INDEX copyright_imageid_index ON ImageCopyright (imageid); CREATE INDEX uuid_index ON ImageHistory (uuid); CREATE INDEX subject_relations_index ON ImageRelations (subject); CREATE INDEX object_relations_index ON ImageRelations (object); CREATE INDEX tagproperties_index ON TagProperties (tagid); CREATE INDEX imagetagproperties_index ON ImageTagProperties (imageid, tagid); CREATE INDEX imagetagproperties_imageid_index ON ImageTagProperties (imageid); CREATE INDEX imagetagproperties_tagid_index ON ImageTagProperties (tagid); CREATE TRIGGER delete_albumroot DELETE ON AlbumRoots BEGIN DELETE FROM Albums WHERE Albums.albumRoot = OLD.id; END; CREATE TRIGGER delete_album DELETE ON Albums BEGIN DELETE FROM Images WHERE Images.album = OLD.id; END; CREATE TRIGGER delete_image DELETE ON Images BEGIN DELETE FROM ImageTags WHERE imageid=OLD.id; DELETE From ImageInformation WHERE imageid=OLD.id; DELETE From ImageMetadata WHERE imageid=OLD.id; DELETE From VideoMetadata WHERE imageid=OLD.id; DELETE From ImagePositions WHERE imageid=OLD.id; DELETE From ImageComments WHERE imageid=OLD.id; DELETE From ImageCopyright WHERE imageid=OLD.id; DELETE From ImageProperties WHERE imageid=OLD.id; DELETE From ImageHistory WHERE imageid=OLD.id; DELETE FROM ImageRelations WHERE subject=OLD.id OR object=OLD.id; DELETE FROM ImageTagProperties WHERE imageid=OLD.id; UPDATE Albums SET icon=null WHERE icon=OLD.id; UPDATE Tags SET icon=null WHERE icon=OLD.id; END; CREATE TRIGGER delete_tag DELETE ON Tags BEGIN DELETE FROM ImageTags WHERE tagid=OLD.id; DELETE FROM TagProperties WHERE tagid=OLD.id; DELETE FROM ImageTagProperties WHERE tagid=OLD.id; END; CREATE TRIGGER insert_tagstree AFTER INSERT ON Tags BEGIN INSERT INTO TagsTree SELECT NEW.id, NEW.pid UNION SELECT NEW.id, pid FROM TagsTree WHERE id=NEW.pid; END; CREATE TRIGGER delete_tagstree DELETE ON Tags BEGIN DELETE FROM Tags WHERE id IN (SELECT id FROM TagsTree WHERE pid=OLD.id); DELETE FROM TagsTree WHERE id IN (SELECT id FROM TagsTree WHERE pid=OLD.id); DELETE FROM TagsTree WHERE id=OLD.id; END; CREATE TRIGGER move_tagstree UPDATE OF pid ON Tags BEGIN DELETE FROM TagsTree WHERE ((id = OLD.id) OR id IN (SELECT id FROM TagsTree WHERE pid=OLD.id)) AND pid IN (SELECT pid FROM TagsTree WHERE id=OLD.id); INSERT INTO TagsTree SELECT NEW.id, NEW.pid UNION SELECT NEW.id, pid FROM TagsTree WHERE id=NEW.pid UNION SELECT id, NEW.pid FROM TagsTree WHERE pid=NEW.id UNION SELECT A.id, B.pid FROM TagsTree A, TagsTree B WHERE A.pid = NEW.id AND B.id = NEW.pid; END; SELECT Albums.relativePath, Images.name FROM Images INNER JOIN Albums ON Albums.id=Images.album WHERE Albums.id=:albumID ORDER BY Images.name COLLATE NOCASE; SELECT Albums.relativePath, Images.name FROM Images INNER JOIN Albums ON Albums.id=Images.album WHERE Albums.id=:albumID ORDER BY Albums.relativePath,Images.name; SELECT Albums.relativePath, Images.name FROM Images INNER JOIN Albums ON Albums.id=Images.album INNER JOIN ImageInformation ON ImageInformation.imageid=Images.id WHERE Albums.id=:albumID ORDER BY ImageInformation.creationDate; SELECT Albums.relativePath, Images.name FROM Images INNER JOIN Albums ON Albums.id=Images.album INNER JOIN ImageInformation ON ImageInformation.imageid=Images.id WHERE Albums.id=:albumID ORDER BY ImageInformation.rating DESC; SELECT Albums.relativePath, Images.name FROM Images INNER JOIN Albums ON Albums.id=Images.album WHERE Albums.id=:albumID; + + INSERT OR IGNORE INTO Images ( id, :fieldList ) VALUES ( :id, :valueList ); + UPDATE Images SET :fieldValueList WHERE id=:id; + + INSERT OR IGNORE INTO ImageInformation ( imageid, :fieldList ) VALUES ( :id, :valueList ); UPDATE ImageInformation SET :fieldValueList WHERE imageid=:id; INSERT OR IGNORE INTO ImageHistory ( imageid, :fieldList ) VALUES ( :id, :valueList ); UPDATE ImageHistory SET :fieldValueList WHERE imageid=:id; INSERT INTO Tags (pid, name) VALUES( :tagPID, :tagname); DELETE FROM Tags WHERE id=:tagID; DELETE FROM Albums WHERE albumRoot=:albumRoot; DELETE FROM Albums WHERE albumRoot=:albumRoot AND relativePath=:relativePath; DELETE FROM Albums WHERE Albums.id=:albumId; SELECT Albums.albumRoot, Albums.relativePath, Images.name FROM Images JOIN Albums ON Albums.id=Images.album WHERE Images.status=1 AND Images.id IN (SELECT imageid FROM ImageTags WHERE tagid=:tagID OR tagid IN (SELECT id FROM TagsTree WHERE pid=:tagID2) ); SELECT Albums.albumRoot, Albums.relativePath, Images.name FROM Images JOIN Albums ON Albums.id=Images.album WHERE Images.status=1 AND Images.id IN (SELECT imageid FROM ImageTags WHERE tagid=:tagID); SELECT imageid FROM ImageTags JOIN Images ON ImageTags.imageid=Images.id WHERE Images.status=1 AND ( tagid=:tagID OR tagid IN (SELECT id FROM TagsTree WHERE pid=:tagPID) ); SELECT imageid FROM ImageTags JOIN Images ON ImageTags.imageid=Images.id WHERE Images.status=1 AND tagid=:tagID; SELECT DISTINCT Images.id, Images.name, Images.album, Albums.albumRoot, ImageInformation.rating, Images.category, ImageInformation.format, ImageInformation.creationDate, Images.modificationDate, Images.fileSize, ImageInformation.width, ImageInformation.height FROM Images INNER JOIN ImageInformation ON Images.id=ImageInformation.imageid INNER JOIN Albums ON Albums.id=Images.album WHERE Images.status=1 AND Images.id IN (SELECT imageid FROM ImageTags WHERE tagid=:tagID OR tagid IN (SELECT id FROM TagsTree WHERE pid=:tagPID)); SELECT DISTINCT Images.id, Images.name, Images.album, Albums.albumRoot, ImageInformation.rating, Images.category, ImageInformation.format, ImageInformation.creationDate, Images.modificationDate, Images.fileSize, ImageInformation.width, ImageInformation.height FROM Images INNER JOIN ImageInformation ON Images.id=ImageInformation.imageid INNER JOIN Albums ON Albums.id=Images.album WHERE Images.status=1 AND Images.id IN (SELECT imageid FROM ImageTags WHERE tagid=:tagID ); CREATE TABLE Thumbnails (id INTEGER PRIMARY KEY, type INTEGER, modificationDate DATETIME, orientationHint INTEGER, data BLOB); CREATE TABLE UniqueHashes (uniqueHash TEXT, fileSize INTEGER, thumbId INTEGER, UNIQUE(uniqueHash, fileSize)); CREATE TABLE FilePaths (path TEXT, thumbId INTEGER, UNIQUE(path)); CREATE TABLE CustomIdentifiers (identifier TEXT, thumbId INTEGER, UNIQUE(identifier)); CREATE TABLE IF NOT EXISTS Settings (keyword TEXT NOT NULL UNIQUE, value TEXT); CREATE INDEX id_uniqueHashes ON UniqueHashes (thumbId); CREATE INDEX id_filePaths ON FilePaths (thumbId); CREATE INDEX id_customIdentifiers ON CustomIdentifiers (thumbId); CREATE TRIGGER delete_thumbnails DELETE ON Thumbnails BEGIN DELETE FROM UniqueHashes WHERE UniqueHashes.thumbId = OLD.id; DELETE FROM FilePaths WHERE FilePaths.thumbId = OLD.id; DELETE FROM CustomIdentifiers WHERE CustomIdentifiers.thumbId = OLD.id; END; SELECT value FROM Settings WHERE keyword=:keyword; SELECT value FROM Settings WHERE keyword=:keyword; REPLACE INTO Settings VALUES (:keyword, :value); CREATE TABLE IF NOT EXISTS Settings (keyword TEXT NOT NULL UNIQUE, value TEXT); CREATE TABLE Identities (id INTEGER PRIMARY KEY, type INTEGER); CREATE TABLE IdentityAttributes (id INTEGER, attribute TEXT, value TEXT); CREATE TABLE OpenCVLBPHRecognizer (id INTEGER PRIMARY KEY, version INTEGER, radius INTEGER, neighbors INTEGER, grid_x INTEGER, grid_y INTEGER); CREATE TABLE OpenCVLBPHistograms (id INTEGER PRIMARY KEY, recognizerid INTEGER, identity INTEGER, context TEXT, type INTEGER, rows INTEGER, cols INTEGER, data BLOB); CREATE TABLE FaceMatrices (id INTEGER PRIMARY KEY, identity INTEGER, context TEXT, type INTEGER, rows INTEGER, cols INTEGER, data BLOB, vecdata BLOB); CREATE INDEX attribute_index ON IdentityAttributes (id); CREATE TRIGGER delete_identities DELETE ON Identities BEGIN DELETE FROM IdentityAttributes WHERE IdentityAttributes.id = OLD.id; END; SELECT value FROM Settings WHERE keyword=:keyword; REPLACE INTO Settings VALUES (:keyword, :value); CREATE TABLE IF NOT EXISTS ImageSimilarity (imageid1 INTEGER NOT NULL, imageid2 INTEGER NOT NULL, algorithm INTEGER, value DOUBLE, CONSTRAINT Similar UNIQUE(imageid1, imageid2, algorithm)); CREATE TABLE IF NOT EXISTS ImageHaarMatrix (imageid INTEGER PRIMARY KEY, modificationDate DATETIME, uniqueHash TEXT, matrix BLOB); CREATE TABLE IF NOT EXISTS SimilaritySettings (keyword TEXT NOT NULL UNIQUE, value TEXT); CREATE TRIGGER IF NOT EXISTS delete_similarities DELETE ON ImageHaarMatrix BEGIN DELETE FROM ImageSimilarity WHERE ( ImageSimilarity.imageid1=OLD.imageid OR ImageSimilarity.imageid2=OLD.imageid ) AND ( ImageSimilarity.algorithm=1 ); END; SELECT value FROM SimilaritySettings WHERE keyword=:keyword; SELECT value FROM SimilaritySettings WHERE keyword=:keyword; REPLACE INTO SimilaritySettings VALUES (:keyword, :value); SELECT id, label, status, type, identifier, specificPath FROM AlbumRoots; INSERT OR IGNORE INTO AlbumRoots (id, label, status, type, identifier, specificPath) VALUES (:id, :label, :status, :type, :identifier, :specificPath); SELECT id, albumRoot, relativePath, date, caption, collection FROM Albums WHERE albumRoot IN (SELECT id FROM AlbumRoots); INSERT OR IGNORE INTO Albums (id, albumRoot, relativePath, date, caption, collection, icon) VALUES (:id, :albumRoot, :relativePath, :date, :caption, :collection, NULL); SELECT id, icon FROM Albums WHERE icon IS NOT NULL AND icon != 0; UPDATE Albums set icon = :icon WHERE id = :id; - SELECT id, album, name, status, category, modificationDate, fileSize, uniqueHash FROM Images + SELECT id, album, name, status, category, modificationDate, fileSize, uniqueHash, manualOrder FROM Images WHERE album IN (SELECT id FROM Albums); - INSERT OR IGNORE INTO Images (id, album, name, status, category, modificationDate, fileSize, uniqueHash) VALUES (:id, :album, :name, :status, :category, :modificationDate, :fileSize, :uniqueHash); + INSERT OR IGNORE INTO Images (id, album, name, status, category, modificationDate, fileSize, uniqueHash, manualOrder) VALUES (:id, :album, :name, :status, :category, :modificationDate, :fileSize, :uniqueHash, :manualOrder); SELECT imageid, rating, creationDate, digitizationDate, orientation, width, height, format, colorDepth, colorModel FROM ImageInformation WHERE imageid IN (SELECT id FROM Images); INSERT OR IGNORE INTO ImageInformation (imageid, rating, creationDate, digitizationDate, orientation, width, height, format, colorDepth, colorModel) VALUES (:imageid, :rating, :creationDate, :digitizationDate, :orientation, :width, :height, :format, :colorDepth, :colorModel); SELECT imageid, make, model, lens, aperture, focalLength, focalLength35, exposureTime, exposureProgram, exposureMode, sensitivity, flash, whiteBalance, whiteBalanceColorTemperature, meteringMode, subjectDistance, subjectDistanceCategory FROM ImageMetadata WHERE imageid IN (SELECT id FROM Images); INSERT OR IGNORE INTO ImageMetadata (imageid, make, model, lens, aperture, focalLength, focalLength35, exposureTime, exposureProgram, exposureMode, sensitivity, flash, whiteBalance, whiteBalanceColorTemperature, meteringMode, subjectDistance, subjectDistanceCategory) VALUES (:imageid, :make, :model, :lens, :aperture, :focalLength, :focalLength35, :exposureTime, :exposureProgram, :exposureMode, :sensitivity, :flash, :whiteBalance, :whiteBalanceColorTemperature, :meteringMode, :subjectDistance, :subjectDistanceCategory); SELECT imageid, aspectRatio, audioBitRate, audioChannelType, audioCompressor, duration, frameRate, videoCodec FROM VideoMetadata WHERE imageid IN (SELECT id FROM Images); INSERT OR IGNORE INTO VideoMetadata (imageid, aspectRatio, audioBitRate, audioChannelType, audioCompressor, duration, frameRate, videoCodec) VALUES (:imageid, :aspectRatio, :audioBitRate, :audioChannelType, :audioCompressor, :duration, :frameRate, :videoCodec); SELECT imageid, tagid, property, value FROM ImageTagProperties WHERE imageid IN (SELECT id FROM Images); INSERT OR IGNORE INTO ImageTagProperties (imageid, tagid, property, value) VALUES (:imageid, :tagid, :property, :value); SELECT tagid, property, value FROM TagProperties; INSERT OR IGNORE INTO TagProperties (tagid, property, value) VALUES (:tagid, :property, :value); SELECT imageid, latitude, latitudeNumber, longitude, longitudeNumber, altitude, orientation, tilt, roll, accuracy, description FROM ImagePositions WHERE imageid IN (SELECT id FROM Images); INSERT OR IGNORE INTO ImagePositions (imageid, latitude, latitudeNumber, longitude, longitudeNumber, altitude, orientation, tilt, roll, accuracy, description) VALUES (:imageid, :latitude, :latitudeNumber, :longitude, :longitudeNumber, :altitude, :orientation, :tilt, :roll, :accuracy, :description); SELECT id, imageid, type, language, author, date, comment FROM ImageComments WHERE imageid IN (SELECT id FROM Images); INSERT OR IGNORE INTO ImageComments (id, imageid, type, language, author, date, comment) VALUES (:id, :imageid, :type, :language, :author, :date, :comment); SELECT id, imageid, property, value, extraValue FROM ImageCopyright WHERE imageid IN (SELECT id FROM Images); INSERT OR IGNORE INTO ImageCopyright (id, imageid, property, value, extraValue) VALUES (:id, :imageid, :property, :value, :extraValue); SELECT id, pid, name, CASE WHEN icon = 0 THEN NULL ELSE icon END AS icon, iconkde FROM Tags WHERE id != 0; INSERT OR REPLACE INTO Tags (id, pid, name, icon, iconkde) VALUES (:id, :pid, :name, :icon, :iconkde); SELECT imageid, tagid FROM ImageTags WHERE imageid IN (SELECT id FROM Images); INSERT OR IGNORE INTO ImageTags (imageid, tagid) VALUES (:imageid, :tagid); SELECT imageid, property, value FROM ImageProperties WHERE imageid IN (SELECT id FROM Images); INSERT OR IGNORE INTO ImageProperties (imageid, property, value) VALUES (:imageid, :property, :value); SELECT imageid, uuid, history FROM ImageHistory WHERE imageid IN (SELECT id FROM Images); INSERT OR IGNORE INTO ImageHistory (imageid, uuid, history) VALUES (:imageid, :uuid, :history); SELECT subject, object, type FROM ImageRelations INNER JOIN Images ON subject = Images.id WHERE object IN (SELECT id FROM Images); INSERT OR IGNORE INTO ImageRelations (subject, object, type) VALUES (:subject, :object, :type); SELECT id, type, name, query FROM Searches; INSERT OR IGNORE INTO Searches (id, type, name, query) VALUES (:id, :type, :name, :query); SELECT id, identifier, filename, filesize, filedate FROM DownloadHistory; INSERT OR IGNORE INTO DownloadHistory (id, identifier, filename, filesize, filedate) VALUES (:id, :identifier, :filename, :filesize, :filedate); SELECT keyword, value FROM Settings WHERE keyword = 'Locale'; INSERT OR IGNORE INTO Settings (keyword, value) VALUES (:keyword, :value); DELETE FROM Thumbnails WHERE id IN (SELECT thumbId FROM FilePaths WHERE path=:path); DELETE FROM Thumbnails WHERE id IN (SELECT thumbId FROM UniqueHashes WHERE uniqueHash=:uniqueHash AND fileSize=:filesize); DELETE FROM Thumbnails WHERE id IN (SELECT thumbId FROM CustomIdentifiers WHERE identifier=:identifier); CREATE TABLE ImageHistory (imageid INTEGER PRIMARY KEY, uuid TEXT, history TEXT); CREATE TABLE ImageRelations (subject INTEGER, object INTEGER, type INTEGER, UNIQUE(subject, object, type)); CREATE TABLE TagProperties (tagid INTEGER, property TEXT, value TEXT); CREATE TABLE ImageTagProperties (imageid INTEGER, tagid INTEGER, property TEXT, value TEXT); CREATE INDEX tag_id_index ON ImageTags (imageid); CREATE INDEX image_name_index ON Images (name); CREATE INDEX creationdate_index ON ImageInformation (creationDate); CREATE INDEX comments_imageid_index ON ImageComments (imageid); CREATE INDEX copyright_imageid_index ON ImageCopyright (imageid); CREATE INDEX uuid_index ON ImageHistory (uuid); CREATE INDEX subject_relations_index ON ImageRelations (subject); CREATE INDEX object_relations_index ON ImageRelations (object); CREATE INDEX tagproperties_index ON TagProperties (tagid); CREATE INDEX imagetagproperties_index ON ImageTagProperties (imageid, tagid); CREATE INDEX imagetagproperties_imageid_index ON ImageTagProperties (imageid); CREATE INDEX imagetagproperties_tagid_index ON ImageTagProperties (tagid); DROP TRIGGER delete_image; CREATE TRIGGER delete_image DELETE ON Images BEGIN DELETE FROM ImageTags WHERE imageid=OLD.id; DELETE From ImageHaarMatrix WHERE imageid=OLD.id; DELETE From ImageInformation WHERE imageid=OLD.id; DELETE From ImageMetadata WHERE imageid=OLD.id; DELETE From VideoMetadata WHERE imageid=OLD.id; DELETE From ImagePositions WHERE imageid=OLD.id; DELETE From ImageComments WHERE imageid=OLD.id; DELETE From ImageCopyright WHERE imageid=OLD.id; DELETE From ImageProperties WHERE imageid=OLD.id; DELETE From ImageHistory WHERE imageid=OLD.id; DELETE FROM ImageRelations WHERE subject=OLD.id OR object=OLD.id; DELETE FROM ImageTagProperties WHERE imageid=OLD.id; UPDATE Albums SET icon=null WHERE icon=OLD.id; UPDATE Tags SET icon=null WHERE icon=OLD.id; END; DROP TRIGGER delete_tag; CREATE TRIGGER delete_tag DELETE ON Tags BEGIN DELETE FROM ImageTags WHERE tagid=OLD.id; DELETE FROM TagProperties WHERE tagid=OLD.id; DELETE FROM ImageTagProperties WHERE tagid=OLD.id; END; CREATE TABLE VideoMetadata (imageid INTEGER PRIMARY KEY, aspectRatio TEXT, audioBitRate TEXT, audioChannelType TEXT, audioCompressor TEXT, duration TEXT, frameRate TEXT, exposureProgram INTEGER, videoCodec TEXT); DROP TRIGGER delete_image; CREATE TRIGGER delete_image DELETE ON Images BEGIN DELETE FROM ImageTags WHERE imageid=OLD.id; DELETE From ImageHaarMatrix WHERE imageid=OLD.id; DELETE From ImageInformation WHERE imageid=OLD.id; DELETE From ImageMetadata WHERE imageid=OLD.id; DELETE From VideoMetadata WHERE imageid=OLD.id; DELETE From ImagePositions WHERE imageid=OLD.id; DELETE From ImageComments WHERE imageid=OLD.id; DELETE From ImageCopyright WHERE imageid=OLD.id; DELETE From ImageProperties WHERE imageid=OLD.id; DELETE From ImageHistory WHERE imageid=OLD.id; DELETE FROM ImageRelations WHERE subject=OLD.id OR object=OLD.id; DELETE FROM ImageTagProperties WHERE imageid=OLD.id; UPDATE Albums SET icon=null WHERE icon=OLD.id; UPDATE Tags SET icon=null WHERE icon=OLD.id; END; DROP TABLE IF EXISTS ImageHaarMatrix; DROP TRIGGER delete_image; CREATE TRIGGER delete_image DELETE ON Images BEGIN DELETE FROM ImageTags WHERE imageid=OLD.id; DELETE From ImageInformation WHERE imageid=OLD.id; DELETE From ImageMetadata WHERE imageid=OLD.id; DELETE From VideoMetadata WHERE imageid=OLD.id; DELETE From ImagePositions WHERE imageid=OLD.id; DELETE From ImageComments WHERE imageid=OLD.id; DELETE From ImageCopyright WHERE imageid=OLD.id; DELETE From ImageProperties WHERE imageid=OLD.id; DELETE From ImageHistory WHERE imageid=OLD.id; DELETE FROM ImageRelations WHERE subject=OLD.id OR object=OLD.id; DELETE FROM ImageTagProperties WHERE imageid=OLD.id; UPDATE Albums SET icon=null WHERE icon=OLD.id; UPDATE Tags SET icon=null WHERE icon=OLD.id; END; + ALTER TABLE Images ADD manualOrder INTEGER; CREATE TABLE CustomIdentifiers (identifier TEXT, thumbId INTEGER, UNIQUE(identifier)); CREATE INDEX id_customIdentifiers ON CustomIdentifiers (thumbId); DROP TRIGGER delete_thumbnails; CREATE TRIGGER delete_thumbnails DELETE ON Thumbnails BEGIN DELETE FROM UniqueHashes WHERE UniqueHashes.thumbId = OLD.id; DELETE FROM FilePaths WHERE FilePaths.thumbId = OLD.id; DELETE FROM CustomIdentifiers WHERE CustomIdentifiers.thumbId = OLD.id; END; VACUUM; VACUUM; VACUUM; VACUUM; pragma integrity_check; pragma integrity_check; pragma integrity_check; pragma integrity_check; $$DBHOSTNAME$$ digikam root $$DBPORT$$ $$DBOPTIONS$$ CREATE TABLE IF NOT EXISTS PrivCheck (id INT, name VARCHAR(35)) ENGINE InnoDB; ALTER TABLE PrivCheck DROP COLUMN name; DROP TABLE PrivCheck; DROP TABLE IF EXISTS PrivCheck; CREATE TABLE IF NOT EXISTS AlbumRoots (id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, label LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, status INTEGER NOT NULL, type INTEGER NOT NULL, identifier LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, specificPath LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, UNIQUE(identifier(127), specificPath(128))) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS Albums (id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, albumRoot INTEGER NOT NULL, relativePath LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, date DATE, caption LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, collection LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, icon INTEGER, CONSTRAINT Albums_AlbumRoots FOREIGN KEY (albumRoot) REFERENCES AlbumRoots (id) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE(albumRoot, relativePath(255))) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS Images (id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, album INTEGER, name LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, status INTEGER NOT NULL, category INTEGER NOT NULL, modificationDate DATETIME(3), fileSize BIGINT, uniqueHash VARCHAR(128), + manualOrder INTEGER, CONSTRAINT Images_Albums FOREIGN KEY (album) REFERENCES Albums (id) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE (album, name(255))) ENGINE InnoDB; ALTER TABLE Albums ADD CONSTRAINT Albums_Images FOREIGN KEY (icon) REFERENCES Images (id) ON DELETE SET NULL ON UPDATE CASCADE; CREATE TABLE IF NOT EXISTS ImageInformation (imageid INTEGER PRIMARY KEY, rating INTEGER, creationDate DATETIME(3), digitizationDate DATETIME(3), orientation INTEGER, width INTEGER, height INTEGER, format LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, colorDepth INTEGER, colorModel INTEGER, CONSTRAINT ImageInformation_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS ImageMetadata (imageid INTEGER PRIMARY KEY, make LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, model LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, lens LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, aperture REAL, focalLength REAL, focalLength35 REAL, exposureTime REAL, exposureProgram INTEGER, exposureMode INTEGER, sensitivity INTEGER, flash INTEGER, whiteBalance INTEGER, whiteBalanceColorTemperature INTEGER, meteringMode INTEGER, subjectDistance REAL, subjectDistanceCategory INTEGER, CONSTRAINT ImageMetadata_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS VideoMetadata (imageid INTEGER PRIMARY KEY, aspectRatio TEXT, audioBitRate TEXT, audioChannelType TEXT, audioCompressor TEXT, duration TEXT, frameRate TEXT, exposureProgram INTEGER, videoCodec TEXT, CONSTRAINT VideoMetadata_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS ImagePositions (imageid INTEGER PRIMARY KEY, latitude LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, latitudeNumber REAL, longitude LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, longitudeNumber REAL, altitude REAL, orientation REAL, tilt REAL, roll REAL, accuracy REAL, description LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, CONSTRAINT ImagePositions_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS ImageComments (id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, imageid INTEGER, type INTEGER, language VARCHAR(128) CHARACTER SET utf8 COLLATE utf8_general_ci, author LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, date DATETIME(3), comment LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, CONSTRAINT ImageComments_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE(imageid, type, language, author(202))) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS ImageCopyright (id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, imageid INTEGER, property LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, value LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, extraValue LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, CONSTRAINT ImageCopyright_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE(imageid, property(110), value(111), extraValue(111))) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS Tags (id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, pid INTEGER, name LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, icon INTEGER, iconkde LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, lft INT NOT NULL, rgt INT NOT NULL, CONSTRAINT Tags_Images FOREIGN KEY (icon) REFERENCES Images (id) ON DELETE SET NULL ON UPDATE CASCADE, UNIQUE(pid, name(100))) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS ImageTags (imageid INTEGER NOT NULL, tagid INTEGER NOT NULL, CONSTRAINT ImageTags_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT ImageTags_Tags FOREIGN KEY (tagid) REFERENCES Tags (id) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE (imageid, tagid)) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS ImageProperties (imageid INTEGER NOT NULL, property LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, value LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, CONSTRAINT ImageProperties_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE (imageid, property(255))) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS Searches (id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, type INTEGER, name LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, query LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS DownloadHistory (id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, identifier LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, filename LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, filesize BIGINT, filedate DATETIME(3), UNIQUE(identifier(164), filename(165), filesize, filedate)) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS Settings (keyword LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, value LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, UNIQUE(keyword(255))) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS ImageHistory (imageid INTEGER PRIMARY KEY, uuid VARCHAR(128), history LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, CONSTRAINT ImageHistory_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS ImageRelations (subject INTEGER, object INTEGER, type INTEGER, CONSTRAINT ImageRelations_ImagesS FOREIGN KEY (subject) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT ImageRelations_ImagesO FOREIGN KEY (object) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE(subject, object, type)) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS TagProperties (tagid INTEGER, property TEXT CHARACTER SET utf8 COLLATE utf8_general_ci, value LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, CONSTRAINT TagProperties_Tags FOREIGN KEY (tagid) REFERENCES Tags (id) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS ImageTagProperties (imageid INTEGER, tagid INTEGER, property TEXT CHARACTER SET utf8 COLLATE utf8_general_ci, value LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, CONSTRAINT ImageTagProperties_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT ImageTagProperties_Tags FOREIGN KEY (tagid) REFERENCES Tags (id) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE InnoDB; CREATE OR REPLACE VIEW TagsTree AS SELECT id, pid FROM Tags; DROP PROCEDURE IF EXISTS create_index_if_not_exists; CREATE PROCEDURE create_index_if_not_exists(table_name_vc varchar(50), index_name_vc varchar(50), field_list_vc varchar(1024)) BEGIN set @Index_cnt = ( SELECT COUNT(1) cnt FROM INFORMATION_SCHEMA.STATISTICS WHERE CONVERT(DATABASE() USING latin1) = CONVERT(TABLE_SCHEMA USING latin1) AND CONVERT(table_name USING latin1) = CONVERT(table_name_vc USING latin1) AND CONVERT(index_name USING latin1) = CONVERT(index_name_vc USING latin1) ); IF IFNULL(@Index_cnt, 0) = 0 THEN set @index_sql = CONCAT( CONVERT( 'ALTER TABLE ' USING latin1), CONVERT( table_name_vc USING latin1), CONVERT( ' ADD INDEX ' USING latin1), CONVERT( index_name_vc USING latin1), CONVERT( '(' USING latin1), CONVERT( field_list_vc USING latin1), CONVERT( ');' USING latin1) ); PREPARE stmt FROM @index_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; END IF; END; CALL create_index_if_not_exists('Images','dir_index','album'); CALL create_index_if_not_exists('Images','hash_index','uniqueHash'); CALL create_index_if_not_exists('ImageTags','tag_index','tagid'); CALL create_index_if_not_exists('ImageTags','tag_id_index','imageid'); CALL create_index_if_not_exists('Images','image_name_index','name(255)'); CALL create_index_if_not_exists('ImageInformation','creationdate_index','creationDate'); CALL create_index_if_not_exists('ImageComments','comments_imageid_index','imageid'); CALL create_index_if_not_exists('ImageCopyright','copyright_imageid_index','imageid'); CALL create_index_if_not_exists('ImageHistory','uuid_index','uuid'); CALL create_index_if_not_exists('ImageRelations','subject_relations_index','subject'); CALL create_index_if_not_exists('ImageRelations','object_relations_index','object'); CALL create_index_if_not_exists('TagProperties','tagproperties_index','tagid'); CALL create_index_if_not_exists('ImageTagProperties','imagetagproperties_index','imageid, tagid'); CALL create_index_if_not_exists('ImageTagProperties','imagetagproperties_imageid_index','imageid'); CALL create_index_if_not_exists('ImageTagProperties','imagetagproperties_tagid_index','tagid'); SELECT @minLeft := IF(ISNULL(MIN(lft)), 1, MIN(lft)-1), @maxRight := IF(ISNULL(MAX(rgt)), 2, MAX(rgt)+1) FROM Tags WHERE id >= 0 AND pid >= 0; SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO'; REPLACE INTO Tags (id, pid, name, icon, iconkde, lft, rgt) VALUES (0, -1, '_Digikam_root_tag_', NULL, NULL, @minLeft, @maxRight); SET SQL_MODE=@OLD_SQL_MODE; SELECT Albums.relativePath, Images.name FROM Images INNER JOIN Albums ON Albums.id=Images.album WHERE Albums.id=:albumID ORDER BY Images.name; SELECT Albums.relativePath, Images.name FROM Images INNER JOIN Albums ON Albums.id=Images.album WHERE Albums.id=:albumID ORDER BY Images.name; SELECT Albums.relativePath, Images.name FROM Images INNER JOIN Albums ON Albums.id=Images.album WHERE Albums.id=:albumID ORDER BY Albums.relativePath,Images.name; SELECT Albums.relativePath, Images.name FROM Images INNER JOIN Albums ON Albums.id=Images.album INNER JOIN ImageInformation ON ImageInformation.imageid=Images.id WHERE Albums.id=:albumID ORDER BY ImageInformation.creationDate; SELECT Albums.relativePath, Images.name FROM Images INNER JOIN Albums ON Albums.id=Images.album INNER JOIN ImageInformation ON ImageInformation.imageid=Images.id WHERE Albums.id=:albumID ORDER BY ImageInformation.rating DESC; SELECT Albums.relativePath, Images.name FROM Images INNER JOIN Albums ON Albums.id=Images.album WHERE Albums.id=:albumID; + + INSERT INTO Images ( id, :fieldList ) VALUES ( :id, :valueList ) ON DUPLICATE KEY UPDATE :fieldValueList; + + INSERT INTO ImageInformation ( imageid, :fieldList ) VALUES ( :id, :valueList ) ON DUPLICATE KEY UPDATE :fieldValueList; INSERT INTO ImageHistory( imageid, :fieldList ) VALUES ( :id, :valueList ) ON DUPLICATE KEY UPDATE :fieldValueList; SELECT @myLeft := lft FROM Tags WHERE id = :tagPID; SELECT @myLeft := IF (@myLeft is null, 0, @myLeft); UPDATE Tags SET rgt = rgt + 2 WHERE rgt > @myLeft; UPDATE Tags SET lft = lft + 2 WHERE lft > @myLeft; INSERT INTO Tags(name, pid, lft, rgt) VALUES(:tagname, :tagPID, @myLeft + 1, @myLeft + 2); SELECT @myLeft := lft FROM Tags WHERE id = :tagID; SELECT @myLeft := IF (@myLeft is null, 0, @myLeft); DELETE FROM Tags WHERE id = :tagID; UPDATE Tags SET rgt = rgt - 2 WHERE rgt > @myLeft; UPDATE Tags SET lft = lft - 2 WHERE lft > @myLeft; SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1 FROM Tags WHERE id = :tagID; UPDATE Tags SET rgt = rgt * -1, lft = lft * -1 WHERE lft BETWEEN @myLeft AND @myRight; UPDATE Tags SET rgt = rgt - @myWidth WHERE rgt > @myRight; UPDATE Tags SET lft = lft - @myWidth WHERE lft > @myRight; SELECT @myNewLeft := lft FROM Tags WHERE id = :newTagPID; SELECT @myNewLeft := IF (@myNewLeft is null, 0, @myNewLeft); UPDATE Tags SET rgt = rgt + @myWidth WHERE rgt > @myNewLeft; UPDATE Tags SET lft = lft + @myWidth WHERE lft > @myNewLeft; UPDATE Tags SET lft = lft * -1 - @myLeft + @myNewLeft + 1, rgt = rgt * -1 - @myLeft + @myNewLeft + 1 WHERE lft * -1 BETWEEN @myLeft AND @myRight; SELECT @albumID:=id FROM Albums WHERE albumRoot=:albumRoot; DELETE FROM Albums WHERE albumRoot=:albumRoot; DELETE FROM Images WHERE Images.album=@albumID; SELECT @albumID:=id FROM Albums WHERE albumRoot=:albumRoot AND relativePath=:relativePath; DELETE FROM Albums WHERE albumRoot=:albumRoot AND relativePath=:relativePath; DELETE FROM Images WHERE Images.album=@albumID; SELECT @albumID:=id FROM Albums WHERE Albums.id=:albumId; DELETE FROM Albums WHERE Albums.id=:albumId; DELETE FROM Images WHERE Images.album=@albumID; SELECT DISTINCT alb.albumRoot, alb.relativePath, img.name FROM ( Images AS img JOIN Albums AS alb ON alb.id = img.album JOIN ImageTags AS ita ON ita.imageid = img.id ) JOIN ( Tags As tp JOIN Tags As tc ON tc.lft BETWEEN tp.lft AND tp.rgt ) ON tc.id = ita.tagID WHERE img.status = 1 AND tp.id = :tagID /* AND tp.id = :tagID2 */ ORDER BY img.name; SELECT alb.albumRoot, alb.relativePath, img.name FROM Albums AS alb JOIN Images AS img ON alb.id = img.album JOIN ImageTags AS it ON it.imageid = img.id WHERE img.status = 1 AND it.tagid = :tagID; SELECT DISTINCT ita.imageid FROM ( Images AS img JOIN ImageTags AS ita ON ita.imageid = img.id ) JOIN ( Tags As tp JOIN Tags As tc ON tc.lft BETWEEN tp.lft AND tp.rgt ) ON tc.id = ita.tagID WHERE img.status = 1 AND tp.id = :tagID ORDER BY img.name; SELECT imageid FROM ImageTags JOIN Images ON ImageTags.imageid=Images.id WHERE Images.status=1 AND tagid=:tagID; SELECT DISTINCT img.id, img.name, img.album, alb.albumRoot, inf.rating, img.category, inf.format, inf.creationDate, img.modificationDate, img.fileSize, inf.width, inf.height FROM ( Images AS img JOIN ImageInformation AS inf ON img.id=inf.imageid JOIN Albums AS alb ON alb.id=img.album JOIN ImageTags AS ita ON ita.imageid = img.id ) JOIN ( Tags As tp JOIN Tags As tc ON tc.lft BETWEEN tp.lft AND tp.rgt ) ON tc.id = ita.tagID WHERE img.status = 1 AND tp.id = :tagID ORDER BY inf.rating DESC, img.name ASC; SELECT DISTINCT img.id, img.name, img.album, alb.albumRoot, inf.rating, img.category, inf.format, inf.creationDate, img.modificationDate, img.fileSize, inf.width, inf.height FROM Images AS img JOIN ImageInformation AS inf ON img.id=inf.imageid JOIN Albums AS alb ON alb.id=img.album JOIN ImageTags AS ita ON ita.imageid = img.id WHERE img.status = 1 AND ita.tagID = :tagID ORDER BY inf.rating DESC, img.name ASC; CREATE TABLE IF NOT EXISTS Thumbnails (id INTEGER PRIMARY KEY AUTO_INCREMENT, type INTEGER, modificationDate DATETIME(3), orientationHint INTEGER, data LONGBLOB) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS UniqueHashes (uniqueHash VARCHAR(128), fileSize BIGINT, thumbId INTEGER, CONSTRAINT UniqueHashes_Thumbnails FOREIGN KEY (thumbId) REFERENCES Thumbnails (id) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE(uniqueHash, fileSize)) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS FilePaths (path LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, thumbId INTEGER, CONSTRAINT FilePaths_Thumbnails FOREIGN KEY (thumbId) REFERENCES Thumbnails (id) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE(path(255))) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS CustomIdentifiers (identifier LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, thumbId INTEGER, CONSTRAINT CustomIdentifiers_Thumbnails FOREIGN KEY (thumbId) REFERENCES Thumbnails (id) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE(identifier(255))) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS ThumbSettings (keyword LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, value LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, UNIQUE(keyword(255))) ENGINE InnoDB; DROP PROCEDURE IF EXISTS create_index_if_not_exists; CREATE PROCEDURE create_index_if_not_exists(table_name_vc varchar(50), index_name_vc varchar(50), field_list_vc varchar(1024)) BEGIN set @Index_cnt = ( SELECT COUNT(1) cnt FROM INFORMATION_SCHEMA.STATISTICS WHERE CONVERT(DATABASE() USING latin1) = CONVERT(TABLE_SCHEMA USING latin1) AND CONVERT(table_name USING latin1) = CONVERT(table_name_vc USING latin1) AND CONVERT(index_name USING latin1) = CONVERT(index_name_vc USING latin1) ); IF IFNULL(@Index_cnt, 0) = 0 THEN set @index_sql = CONCAT( CONVERT( 'ALTER TABLE ' USING latin1), CONVERT( table_name_vc USING latin1), CONVERT( ' ADD INDEX ' USING latin1), CONVERT( index_name_vc USING latin1), CONVERT( '(' USING latin1), CONVERT( field_list_vc USING latin1), CONVERT( ');' USING latin1) ); PREPARE stmt FROM @index_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; END IF; END; CALL create_index_if_not_exists('UniqueHashes','id_uniqueHashes','thumbId'); CALL create_index_if_not_exists('FilePaths','id_filePaths','thumbId'); CALL create_index_if_not_exists('CustomIdentifiers','id_customIdentifiers','thumbId'); SELECT value FROM ThumbSettings WHERE keyword=:keyword; SELECT value FROM Settings WHERE keyword=:keyword; REPLACE INTO ThumbSettings VALUES (:keyword, :value); CREATE TABLE IF NOT EXISTS Identities (id INTEGER PRIMARY KEY AUTO_INCREMENT, `type` INTEGER) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS IdentityAttributes (id INTEGER, `type` INTEGER, attribute LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, `value` LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, CONSTRAINT IdentityAttributes_Identities FOREIGN KEY (id) REFERENCES Identities (id) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS FaceSettings (keyword LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, `value` LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, UNIQUE(keyword(255))) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS OpenCVLBPHRecognizer (id INTEGER PRIMARY KEY AUTO_INCREMENT, version INTEGER, radius INTEGER, neighbors INTEGER, grid_x INTEGER, grid_y INTEGER) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS OpenCVLBPHistograms (id INTEGER PRIMARY KEY AUTO_INCREMENT, recognizerid INTEGER, identity INTEGER, `context` LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, `type` INTEGER, `rows` INTEGER, `cols` INTEGER, `data` LONGBLOB) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS FaceMatrices (id INTEGER PRIMARY KEY AUTO_INCREMENT, identity INTEGER, `context` LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, `type` INTEGER, `rows` INTEGER, `cols` INTEGER, `data` LONGBLOB, vecdata LONGBLOB) ENGINE InnoDB; DROP PROCEDURE IF EXISTS create_index_if_not_exists; CREATE PROCEDURE create_index_if_not_exists(table_name_vc varchar(50), index_name_vc varchar(50), field_list_vc varchar(1024)) BEGIN set @Index_cnt = ( SELECT COUNT(1) cnt FROM INFORMATION_SCHEMA.STATISTICS WHERE CONVERT(DATABASE() USING latin1) = CONVERT(TABLE_SCHEMA USING latin1) AND CONVERT(table_name USING latin1) = CONVERT(table_name_vc USING latin1) AND CONVERT(index_name USING latin1) = CONVERT(index_name_vc USING latin1) ); IF IFNULL(@Index_cnt, 0) = 0 THEN set @index_sql = CONCAT( CONVERT( 'ALTER TABLE ' USING latin1), CONVERT( table_name_vc USING latin1), CONVERT( ' ADD INDEX ' USING latin1), CONVERT( index_name_vc USING latin1), CONVERT( '(' USING latin1), CONVERT( field_list_vc USING latin1), CONVERT( ');' USING latin1) ); PREPARE stmt FROM @index_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; END IF; END; CALL create_index_if_not_exists('IdentityAttributes','identityattributes_index','id'); SELECT value FROM FaceSettings WHERE keyword=:keyword; REPLACE INTO FaceSettings VALUES (:keyword, :value); CREATE TABLE IF NOT EXISTS ImageSimilarity (imageid1 INTEGER NOT NULL, imageid2 INTEGER NOT NULL, algorithm INTEGER, value DOUBLE, CONSTRAINT Similar UNIQUE(imageid1, imageid2, algorithm)) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS ImageHaarMatrix (imageid INTEGER PRIMARY KEY, modificationDate DATETIME(3), uniqueHash LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, matrix LONGBLOB) ENGINE InnoDB; CREATE TABLE IF NOT EXISTS SimilaritySettings (keyword LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, `value` LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci, UNIQUE(keyword(255))) ENGINE InnoDB; SELECT value FROM SimilaritySettings WHERE keyword=:keyword; SELECT value FROM SimilaritySettings WHERE keyword=:keyword; REPLACE INTO SimilaritySettings VALUES (:keyword, :value); ALTER TABLE Albums DROP FOREIGN KEY Albums_Images; SELECT id, label, status, type, identifier, specificPath FROM AlbumRoots; INSERT IGNORE INTO AlbumRoots (id, label, status, type, identifier, specificPath) VALUES (:id, :label, :status, :type, :identifier, :specificPath); SELECT id, albumRoot, relativePath, date, caption, collection FROM Albums WHERE albumRoot IN (SELECT id FROM AlbumRoots); INSERT IGNORE INTO Albums (id, albumRoot, relativePath, date, caption, collection, icon) VALUES (:id, :albumRoot, :relativePath, :date, :caption, :collection, NULL); SELECT id, icon FROM Albums WHERE icon IS NOT NULL AND icon != 0; UPDATE Albums set icon = :icon WHERE id = :id; - SELECT id, album, name, status, category, modificationDate, fileSize, uniqueHash FROM Images + SELECT id, album, name, status, category, modificationDate, fileSize, uniqueHash, manualOrder FROM Images WHERE album IN (SELECT id FROM Albums); - INSERT IGNORE INTO Images (id, album, name, status, category, modificationDate, fileSize, uniqueHash) VALUES (:id, :album, :name, :status, :category, :modificationDate, :fileSize, :uniqueHash); + INSERT IGNORE INTO Images (id, album, name, status, category, modificationDate, fileSize, uniqueHash, manualOrder) VALUES (:id, :album, :name, :status, :category, :modificationDate, :fileSize, :uniqueHash, :manualOrder); SELECT imageid, rating, creationDate, digitizationDate, orientation, width, height, format, colorDepth, colorModel FROM ImageInformation WHERE imageid IN (SELECT id FROM Images); INSERT IGNORE INTO ImageInformation (imageid, rating, creationDate, digitizationDate, orientation, width, height, format, colorDepth, colorModel) VALUES (:imageid, :rating, :creationDate, :digitizationDate, :orientation, :width, :height, :format, :colorDepth, :colorModel); SELECT imageid, make, model, lens, aperture, focalLength, focalLength35, exposureTime, exposureProgram, exposureMode, sensitivity, flash, whiteBalance, whiteBalanceColorTemperature, meteringMode, subjectDistance, subjectDistanceCategory FROM ImageMetadata WHERE imageid IN (SELECT id FROM Images); INSERT IGNORE INTO ImageMetadata (imageid, make, model, lens, aperture, focalLength, focalLength35, exposureTime, exposureProgram, exposureMode, sensitivity, flash, whiteBalance, whiteBalanceColorTemperature, meteringMode, subjectDistance, subjectDistanceCategory) VALUES (:imageid, :make, :model, :lens, :aperture, :focalLength, :focalLength35, :exposureTime, :exposureProgram, :exposureMode, :sensitivity, :flash, :whiteBalance, :whiteBalanceColorTemperature, :meteringMode, :subjectDistance, :subjectDistanceCategory); SELECT imageid, aspectRatio, audioBitRate, audioChannelType, audioCompressor, duration, frameRate, videoCodec FROM VideoMetadata WHERE imageid IN (SELECT id FROM Images); INSERT IGNORE INTO VideoMetadata (imageid, aspectRatio, audioBitRate, audioChannelType, audioCompressor, duration, frameRate, videoCodec) VALUES (:imageid, :aspectRatio, :audioBitRate, :audioChannelType, :audioCompressor, :duration, :frameRate, :videoCodec); SELECT imageid, tagid, property, value FROM ImageTagProperties WHERE imageid IN (SELECT id FROM Images); INSERT IGNORE INTO ImageTagProperties (imageid, tagid, property, value) VALUES (:imageid, :tagid, :property, :value); SELECT tagid, property, value FROM TagProperties WHERE imageid IN (SELECT id FROM Images); INSERT IGNORE INTO TagProperties (tagid, property, value) VALUES (:tagid, :property, :value); SELECT imageid, latitude, latitudeNumber, longitude, longitudeNumber, altitude, orientation, tilt, roll, accuracy, description FROM ImagePositions WHERE imageid IN (SELECT id FROM Images); INSERT IGNORE INTO ImagePositions (imageid, latitude, latitudeNumber, longitude, longitudeNumber, altitude, orientation, tilt, roll, accuracy, description) VALUES (:imageid, :latitude, :latitudeNumber, :longitude, :longitudeNumber, :altitude, :orientation, :tilt, :roll, :accuracy, :description); SELECT id, imageid, type, language, author, date, comment FROM ImageComments WHERE imageid IN (SELECT id FROM Images); INSERT IGNORE INTO ImageComments (id, imageid, type, language, author, date, comment) VALUES (:id, :imageid, :type, :language, :author, :date, :comment); SELECT id, imageid, property, value, extraValue FROM ImageCopyright WHERE imageid IN (SELECT id FROM Images); INSERT IGNORE INTO ImageCopyright (id, imageid, property, value, extraValue) VALUES (:id, :imageid, :property, :value, :extraValue); SELECT id, pid, name, CASE WHEN icon = 0 THEN NULL ELSE icon END AS icon, iconkde FROM Tags WHERE id != 0; SELECT @myLeft := lft FROM Tags WHERE id = :pid; SELECT @myLeft := IF (@myLeft is null, 0, @myLeft); UPDATE Tags SET rgt = rgt + 2 WHERE rgt > @myLeft; UPDATE Tags SET lft = lft + 2 WHERE lft > @myLeft; REPLACE INTO Tags(id, pid, name, icon, iconkde, lft, rgt) VALUES(:id, :pid, :name, :icon, :iconkde, @myLeft + 1, @myLeft + 2); SELECT imageid, tagid FROM ImageTags WHERE imageid IN (SELECT id FROM Images); INSERT IGNORE INTO ImageTags (imageid, tagid) VALUES (:imageid, :tagid); SELECT imageid, property, value FROM ImageProperties WHERE imageid IN (SELECT id FROM Images); INSERT IGNORE INTO ImageProperties (imageid, property, value) VALUES (:imageid, :property, :value); SELECT imageid, uuid, history FROM ImageHistory WHERE imageid IN (SELECT id FROM Images); INSERT IGNORE INTO ImageHistory (imageid, uuid, history) VALUES (:imageid, :uuid, :history); SELECT subject, object, type FROM ImageRelations INNER JOIN Images ON subject = Images.id WHERE object IN (SELECT id FROM Images); INSERT IGNORE INTO ImageRelations (subject, object, type) VALUES (:subject, :object, :type); SELECT id, type, name, query FROM Searches; INSERT IGNORE INTO Searches (id, type, name, query) VALUES (:id, :type, :name, :query); SELECT id, identifier, filename, filesize, filedate FROM DownloadHistory; INSERT IGNORE INTO DownloadHistory (id, identifier, filename, filesize, filedate) VALUES (:id, :identifier, :filename, :filesize, :filedate); SELECT keyword, value FROM Settings WHERE keyword = 'Locale'; INSERT IGNORE INTO Settings (keyword, value) VALUES (:keyword, :value); SELECT @thumbsId := thumbId FROM FilePaths WHERE path=:path; DELETE FROM UniqueHashes WHERE UniqueHashes.thumbId = @thumbsId; DELETE FROM FilePaths WHERE FilePaths.thumbId = @thumbsId; DELETE FROM Thumbnails WHERE id = @thumbsId; SELECT @thumbsId := thumbId FROM UniqueHashes WHERE uniqueHash=:uniqueHash AND fileSize=:filesize; DELETE FROM UniqueHashes WHERE UniqueHashes.thumbId = @thumbsId; DELETE FROM FilePaths WHERE FilePaths.thumbId = @thumbsId; DELETE FROM Thumbnails WHERE id = @thumbsId; SELECT @thumbsId := thumbId FROM CustomIdentifiers WHERE identifier=:identifier; DELETE FROM UniqueHashes WHERE UniqueHashes.thumbId = @thumbsId; DELETE FROM FilePaths WHERE FilePaths.thumbId = @thumbsId; DELETE FROM CustomIdentifiers WHERE CustomIdentifiers.thumbId = @thumbsId; DELETE FROM Thumbnails WHERE id = @thumbsId; CREATE TABLE IF NOT EXISTS ImageHistory (imageid INTEGER PRIMARY KEY, uuid VARCHAR(128), history LONGTEXT CHARACTER SET utf8); CREATE TABLE IF NOT EXISTS ImageRelations (subject INTEGER, object INTEGER, type INTEGER, UNIQUE(subject, object, type)); CREATE TABLE IF NOT EXISTS TagProperties (tagid INTEGER, property TEXT CHARACTER SET utf8, value LONGTEXT CHARACTER SET utf8); CREATE TABLE IF NOT EXISTS ImageTagProperties (imageid INTEGER, tagid INTEGER, property TEXT CHARACTER SET utf8, value LONGTEXT CHARACTER SET utf8); CALL create_index_if_not_exists('ImageTags','tag_id_index','imageid'); CALL create_index_if_not_exists('Images','image_name_index','name(255)'); CALL create_index_if_not_exists('ImageInformation','creationdate_index','creationDate'); CALL create_index_if_not_exists('ImageComments','comments_imageid_index','imageid'); CALL create_index_if_not_exists('ImageCopyright','copyright_imageid_index','imageid'); CALL create_index_if_not_exists('ImageHistory','uuid_index','uuid'); CALL create_index_if_not_exists('ImageRelations','subject_relations_index','subject'); CALL create_index_if_not_exists('ImageRelations','object_relations_index','object'); CALL create_index_if_not_exists('TagProperties','tagproperties_index','tagid'); CALL create_index_if_not_exists('ImageTagProperties','imagetagproperties_index','imageid,tagid'); CALL create_index_if_not_exists('ImageTagProperties','imagetagproperties_imageid_index','imageid'); CALL create_index_if_not_exists('ImageTagProperties','imagetagproperties_tagid_index','tagid'); ALTER TABLE Images CHANGE uniqueHash uniqueHash VARCHAR(128); DROP TRIGGER IF EXISTS delete_image; CREATE TRIGGER delete_image AFTER DELETE ON Images FOR EACH ROW BEGIN DELETE FROM ImageTags WHERE imageid=OLD.id; DELETE From ImageHaarMatrix WHERE imageid=OLD.id; DELETE From ImageInformation WHERE imageid=OLD.id; DELETE From ImageMetadata WHERE imageid=OLD.id; DELETE From VideoMetadata WHERE imageid=OLD.id; DELETE From ImagePositions WHERE imageid=OLD.id; DELETE From ImageComments WHERE imageid=OLD.id; DELETE From ImageCopyright WHERE imageid=OLD.id; DELETE From ImageProperties WHERE imageid=OLD.id; DELETE From ImageHistory WHERE imageid=OLD.id; DELETE FROM ImageRelations WHERE subject=OLD.id OR object=OLD.id; DELETE FROM ImageTagProperties WHERE imageid=OLD.id; UPDATE Albums SET icon=null WHERE icon=OLD.id; UPDATE Tags SET icon=null WHERE icon=OLD.id; END; DROP TRIGGER IF EXISTS delete_tag; CREATE TRIGGER delete_tag AFTER DELETE ON Tags FOR EACH ROW BEGIN DELETE FROM ImageTags WHERE tagid=OLD.id; DELETE FROM TagProperties WHERE tagid=OLD.id; DELETE FROM ImageTagProperties WHERE tagid=OLD.id; END; CREATE TABLE IF NOT EXISTS VideoMetadata (imageid INTEGER PRIMARY KEY, aspectRatio TEXT, audioBitRate TEXT, audioChannelType TEXT, audioCompressor TEXT, duration TEXT, frameRate TEXT, exposureProgram INTEGER, videoCodec TEXT); DROP TRIGGER IF EXISTS delete_image; CREATE TRIGGER delete_image AFTER DELETE ON Images FOR EACH ROW BEGIN DELETE FROM ImageTags WHERE imageid=OLD.id; DELETE From ImageHaarMatrix WHERE imageid=OLD.id; DELETE From ImageInformation WHERE imageid=OLD.id; DELETE From ImageMetadata WHERE imageid=OLD.id; DELETE From VideoMetadata WHERE imageid=OLD.id; DELETE From ImagePositions WHERE imageid=OLD.id; DELETE From ImageComments WHERE imageid=OLD.id; DELETE From ImageCopyright WHERE imageid=OLD.id; DELETE From ImageProperties WHERE imageid=OLD.id; DELETE From ImageHistory WHERE imageid=OLD.id; DELETE FROM ImageRelations WHERE subject=OLD.id OR object=OLD.id; DELETE FROM ImageTagProperties WHERE imageid=OLD.id; UPDATE Albums SET icon=null WHERE icon=OLD.id; UPDATE Tags SET icon=null WHERE icon=OLD.id; END; DROP PROCEDURE IF EXISTS drop_foreign_key; CREATE PROCEDURE drop_foreign_key(IN tableName VARCHAR(64), IN constraintName VARCHAR(64)) BEGIN IF EXISTS( SELECT * FROM information_schema.table_constraints WHERE table_schema = DATABASE() AND table_name = CONVERT(tableName USING latin1) AND constraint_name = CONVERT(constraintName USING latin1) AND constraint_type = 'FOREIGN KEY') THEN SET @query = CONCAT( CONVERT('ALTER TABLE ' USING latin1), CONVERT(tableName USING latin1), CONVERT(' DROP FOREIGN KEY ' USING latin1), CONVERT(constraintName USING latin1), CONVERT(';'USING latin1) ); PREPARE stmt FROM @query; EXECUTE stmt; DEALLOCATE PREPARE stmt; END IF; END; DROP PROCEDURE IF EXISTS drop_index_if_exists; CREATE PROCEDURE drop_index_if_exists(in tableName varchar(64), in indexName varchar(64)) BEGIN IF((SELECT COUNT(*) AS index_exists FROM information_schema.statistics WHERE TABLE_SCHEMA = DATABASE() and table_name = CONVERT(tableName USING latin1) AND index_name = CONVERT(indexName USING latin1)) > 0) THEN SET @query = CONCAT( CONVERT('DROP INDEX ' USING latin1), CONVERT(indexName USING latin1), CONVERT(' ON ' USING latin1), CONVERT(tableName USING latin1) ); PREPARE stmt FROM @query; EXECUTE stmt; END IF; END; DROP TRIGGER IF EXISTS delete_image; DROP TRIGGER IF EXISTS delete_tag; DROP TRIGGER IF EXISTS move_tagstree; CALL drop_index_if_exists('AlbumRoots', 'identifier'); CALL drop_foreign_key('Albums', 'Albums_AlbumRoots'); CALL drop_index_if_exists('Albums', 'albumRoot'); CALL drop_foreign_key('Images', 'Images_Albums'); CALL drop_index_if_exists('Images', 'album'); CALL drop_index_if_exists('Images', 'album_2'); CALL drop_index_if_exists('Images', 'album_3'); CALL drop_foreign_key('Albums', 'Albums_Images'); CALL drop_foreign_key('ImageHaarMatrix', 'ImageHaarMatrix_Images'); CALL drop_foreign_key('ImageInformation', 'ImageInformation_Images'); CALL drop_foreign_key('ImageMetadata', 'ImageMetadata_Images'); CALL drop_foreign_key('VideoMetadata', 'VideoMetadata_Images'); CALL drop_foreign_key('ImagePositions', 'ImagePositions_Images'); CALL drop_foreign_key('ImageComments', 'ImageComments_Images'); CALL drop_index_if_exists('ImageComments', 'imageid'); CALL drop_foreign_key('ImageCopyright', 'ImageCopyright_Images'); CALL drop_index_if_exists('ImageCopyright', 'imageid'); CALL drop_foreign_key('Tags', 'Tags_Images'); CALL drop_index_if_exists('Tags', 'pid'); CALL drop_foreign_key('ImageTags', 'ImageTags_Images'); CALL drop_foreign_key('ImageTags', 'ImageTags_Tags'); CALL drop_foreign_key('ImageProperties', 'ImageProperties_Images'); CALL drop_index_if_exists('ImageProperties', 'imageid'); CALL drop_index_if_exists('DownloadHistory', 'identifier'); CALL drop_index_if_exists('Settings', 'keyword'); CALL drop_foreign_key('ImageHistory', 'ImageHistory_Images'); CALL drop_foreign_key('ImageRelations', 'ImageRelations_ImagesS'); CALL drop_foreign_key('ImageRelations', 'ImageRelations_ImagesO'); CALL drop_foreign_key('TagProperties', 'TagProperties_Tags'); CALL drop_foreign_key('ImageTagProperties', 'ImageTagProperties_Images'); CALL drop_foreign_key('ImageTagProperties', 'ImageTagProperties_Tags'); ALTER TABLE AlbumRoots MODIFY COLUMN label LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE AlbumRoots MODIFY COLUMN identifier LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE AlbumRoots MODIFY COLUMN specificPath LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE Albums MODIFY COLUMN relativePath LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; ALTER TABLE Albums MODIFY COLUMN caption LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE Albums MODIFY COLUMN collection LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE Images MODIFY COLUMN name LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; ALTER TABLE Images MODIFY COLUMN fileSize BIGINT; ALTER TABLE ImageHaarMatrix MODIFY COLUMN uniqueHash LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImageInformation MODIFY COLUMN format LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImageMetadata MODIFY COLUMN make LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImageMetadata MODIFY COLUMN model LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImageMetadata MODIFY COLUMN lens LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImagePositions MODIFY COLUMN latitude LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImagePositions MODIFY COLUMN longitude LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImagePositions MODIFY COLUMN description LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImageComments MODIFY COLUMN language VARCHAR(128) CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImageComments MODIFY COLUMN author LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImageComments MODIFY COLUMN comment LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImageCopyright MODIFY COLUMN property LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImageCopyright MODIFY COLUMN value LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImageCopyright MODIFY COLUMN extraValue LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE Tags MODIFY COLUMN name LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; ALTER TABLE Tags MODIFY COLUMN iconkde LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImageProperties MODIFY COLUMN property LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; ALTER TABLE ImageProperties MODIFY COLUMN value LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; ALTER TABLE Searches MODIFY COLUMN name LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; ALTER TABLE Searches MODIFY COLUMN query LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; ALTER TABLE DownloadHistory MODIFY COLUMN identifier LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE DownloadHistory MODIFY COLUMN filename LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE DownloadHistory MODIFY COLUMN filesize BIGINT; ALTER TABLE Settings MODIFY COLUMN keyword LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL; ALTER TABLE Settings MODIFY COLUMN value LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImageHistory MODIFY COLUMN history LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE TagProperties MODIFY COLUMN property TEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE TagProperties MODIFY COLUMN value LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImageTagProperties MODIFY COLUMN property TEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ImageTagProperties MODIFY COLUMN value LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; DROP TABLE IF EXISTS ImageTagProperties_old; DROP TABLE IF EXISTS TagProperties_old; DROP TABLE IF EXISTS ImageRelations_old; DROP TABLE IF EXISTS ImageHistory_old; DROP TABLE IF EXISTS Settings_old; DROP TABLE IF EXISTS DownloadHistory_old; DROP TABLE IF EXISTS ImageProperties_old; DROP TABLE IF EXISTS ImageTags_old; DROP TABLE IF EXISTS Tags_old; DROP TABLE IF EXISTS ImageCopyright_old; DROP TABLE IF EXISTS ImageComments_old; DROP TABLE IF EXISTS ImagePositions_old; DROP TABLE IF EXISTS VideoMetadata_old; DROP TABLE IF EXISTS ImageMetadata_old; DROP TABLE IF EXISTS ImageInformation_old; DROP TABLE IF EXISTS ImageHaarMatrix_old; DROP TABLE IF EXISTS Images_old; DROP TABLE IF EXISTS Albums_old; DROP TABLE IF EXISTS AlbumRoots_old; RENAME TABLE AlbumRoots TO AlbumRoots_old; CREATE TABLE AlbumRoots LIKE AlbumRoots_old; ALTER TABLE AlbumRoots ADD UNIQUE (identifier(127), specificPath(128)), ENGINE InnoDB; INSERT IGNORE INTO AlbumRoots SELECT * FROM AlbumRoots_old; RENAME TABLE Albums TO Albums_old; CREATE TABLE Albums LIKE Albums_old; ALTER TABLE Albums ADD CONSTRAINT Albums_AlbumRoots FOREIGN KEY (albumRoot) REFERENCES AlbumRoots (id) ON DELETE CASCADE ON UPDATE CASCADE, ADD UNIQUE (albumRoot, relativePath(255)), ENGINE InnoDB; INSERT IGNORE INTO Albums SELECT * FROM Albums_old; UPDATE Albums SET icon = NULL WHERE icon = 0; RENAME TABLE Images TO Images_old; CREATE TABLE Images LIKE Images_old; ALTER TABLE Images ADD CONSTRAINT Images_Albums FOREIGN KEY (album) REFERENCES Albums (id) ON DELETE CASCADE ON UPDATE CASCADE, ADD UNIQUE (album, name(255)), ENGINE InnoDB; INSERT IGNORE INTO Images SELECT * FROM Images_old; ALTER TABLE Albums ADD CONSTRAINT Albums_Images FOREIGN KEY (icon) REFERENCES Images (id) ON DELETE SET NULL ON UPDATE CASCADE; RENAME TABLE ImageHaarMatrix TO ImageHaarMatrix_old; CREATE TABLE ImageHaarMatrix LIKE ImageHaarMatrix_old; ALTER TABLE ImageHaarMatrix ADD CONSTRAINT ImageHaarMatrix_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, ENGINE InnoDB; INSERT IGNORE INTO ImageHaarMatrix SELECT * FROM ImageHaarMatrix_old; RENAME TABLE ImageInformation TO ImageInformation_old; CREATE TABLE ImageInformation LIKE ImageInformation_old; ALTER TABLE ImageInformation ADD CONSTRAINT ImageInformation_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, ENGINE InnoDB; INSERT IGNORE INTO ImageInformation SELECT * FROM ImageInformation_old; RENAME TABLE ImageMetadata TO ImageMetadata_old; CREATE TABLE ImageMetadata LIKE ImageMetadata_old; ALTER TABLE ImageMetadata ADD CONSTRAINT ImageMetadata_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, ENGINE InnoDB; INSERT IGNORE INTO ImageMetadata SELECT * FROM ImageMetadata_old; RENAME TABLE VideoMetadata TO VideoMetadata_old; CREATE TABLE VideoMetadata LIKE VideoMetadata_old; ALTER TABLE VideoMetadata ADD CONSTRAINT VideoMetadata_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, ENGINE InnoDB; INSERT IGNORE INTO VideoMetadata SELECT * FROM VideoMetadata_old; RENAME TABLE ImagePositions TO ImagePositions_old; CREATE TABLE ImagePositions LIKE ImagePositions_old; ALTER TABLE ImagePositions ADD CONSTRAINT ImagePositions_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, ENGINE InnoDB; INSERT IGNORE INTO ImagePositions SELECT * FROM ImagePositions_old; RENAME TABLE ImageComments TO ImageComments_old; CREATE TABLE ImageComments LIKE ImageComments_old; ALTER TABLE ImageComments ADD CONSTRAINT ImageComments_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, ADD UNIQUE(imageid, type, language, author(202)), ENGINE InnoDB; INSERT IGNORE INTO ImageComments SELECT * FROM ImageComments_old; RENAME TABLE ImageCopyright TO ImageCopyright_old; CREATE TABLE ImageCopyright LIKE ImageCopyright_old; ALTER TABLE ImageCopyright ADD CONSTRAINT ImageCopyright_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, ADD UNIQUE(imageid, property(110), value(111), extraValue(111)), ENGINE InnoDB; INSERT IGNORE INTO ImageCopyright SELECT * FROM ImageCopyright_old; SET FOREIGN_KEY_CHECKS=0; RENAME TABLE Tags TO Tags_old; CREATE TABLE Tags LIKE Tags_old; ALTER TABLE Tags ADD CONSTRAINT Tags_Images FOREIGN KEY (icon) REFERENCES Images (id) ON DELETE SET NULL ON UPDATE CASCADE, ADD UNIQUE(pid, name(100)), ENGINE InnoDB; REPLACE INTO Tags SELECT * FROM Tags_old; SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO'; REPLACE INTO Tags (id, pid, name, icon, iconkde, lft, rgt) VALUES (0, -1, '_Digikam_root_tag_', NULL, NULL, (SELECT MIN(tl.lft) FROM Tags AS tl), (SELECT MAX(tr.rgt) FROM Tags AS tr)); SET SQL_MODE=@OLD_SQL_MODE; UPDATE Tags SET icon = NULL WHERE icon = 0; SET FOREIGN_KEY_CHECKS=1; RENAME TABLE ImageTags TO ImageTags_old; CREATE TABLE ImageTags LIKE ImageTags_old; ALTER TABLE ImageTags ADD CONSTRAINT ImageTags_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, ADD CONSTRAINT ImageTags_Tags FOREIGN KEY (tagid) REFERENCES Tags (id) ON DELETE CASCADE ON UPDATE CASCADE, ENGINE InnoDB; INSERT IGNORE INTO ImageTags SELECT * FROM ImageTags_old; RENAME TABLE ImageProperties TO ImageProperties_old; CREATE TABLE ImageProperties LIKE ImageProperties_old; ALTER TABLE ImageProperties ADD CONSTRAINT ImageProperties_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, ADD UNIQUE (imageid, property(255)), ENGINE InnoDB; INSERT IGNORE INTO ImageProperties SELECT * FROM ImageProperties_old; ALTER TABLE Searches ENGINE InnoDB; RENAME TABLE DownloadHistory TO DownloadHistory_old; CREATE TABLE DownloadHistory LIKE DownloadHistory_old; ALTER TABLE DownloadHistory ADD UNIQUE(identifier(164), filename(165), filesize, filedate), ENGINE InnoDB; INSERT IGNORE INTO DownloadHistory SELECT * FROM DownloadHistory_old; RENAME TABLE Settings TO Settings_old; CREATE TABLE Settings LIKE Settings_old; ALTER TABLE Settings ADD UNIQUE(keyword(255)), ENGINE InnoDB; INSERT IGNORE INTO Settings SELECT * FROM Settings_old; RENAME TABLE ImageHistory TO ImageHistory_old; CREATE TABLE ImageHistory LIKE ImageHistory_old; ALTER TABLE ImageHistory ADD CONSTRAINT ImageHistory_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, ENGINE InnoDB; INSERT IGNORE INTO ImageHistory SELECT * FROM ImageHistory_old; RENAME TABLE ImageRelations TO ImageRelations_old; CREATE TABLE ImageRelations LIKE ImageRelations_old; ALTER TABLE ImageRelations ADD CONSTRAINT ImageRelations_ImagesS FOREIGN KEY (subject) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, ADD CONSTRAINT ImageRelations_ImagesO FOREIGN KEY (object) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, ENGINE InnoDB; INSERT IGNORE INTO ImageRelations SELECT * FROM ImageRelations_old; RENAME TABLE TagProperties TO TagProperties_old; CREATE TABLE TagProperties LIKE TagProperties_old; ALTER TABLE TagProperties ADD CONSTRAINT TagProperties_Tags FOREIGN KEY (tagid) REFERENCES Tags (id) ON DELETE CASCADE ON UPDATE CASCADE, ENGINE InnoDB; INSERT IGNORE INTO TagProperties SELECT * FROM TagProperties_old; RENAME TABLE ImageTagProperties TO ImageTagProperties_old; CREATE TABLE ImageTagProperties LIKE ImageTagProperties_old; ALTER TABLE ImageTagProperties ADD CONSTRAINT ImageTagProperties_Images FOREIGN KEY (imageid) REFERENCES Images (id) ON DELETE CASCADE ON UPDATE CASCADE, ADD CONSTRAINT ImageTagProperties_Tags FOREIGN KEY (tagid) REFERENCES Tags (id) ON DELETE CASCADE ON UPDATE CASCADE, ENGINE InnoDB; INSERT IGNORE INTO ImageTagProperties SELECT * FROM ImageTagProperties_old; DROP TABLE ImageTagProperties_old; DROP TABLE TagProperties_old; DROP TABLE ImageRelations_old; DROP TABLE ImageHistory_old; DROP TABLE Settings_old; DROP TABLE DownloadHistory_old; DROP TABLE ImageProperties_old; DROP TABLE ImageTags_old; DROP TABLE Tags_old; DROP TABLE ImageCopyright_old; DROP TABLE ImageComments_old; DROP TABLE ImagePositions_old; DROP TABLE VideoMetadata_old; DROP TABLE ImageMetadata_old; DROP TABLE ImageInformation_old; DROP TABLE ImageHaarMatrix_old; DROP TABLE Images_old; DROP TABLE Albums_old; DROP TABLE AlbumRoots_old; DROP TABLE IF EXISTS ImageHaarMatrix; + ALTER TABLE Images ADD manualOrder INTEGER; ALTER TABLE UniqueHashes CHANGE uniqueHash uniqueHash VARCHAR(128); CREATE TABLE IF NOT EXISTS CustomIdentifiers (identifier LONGTEXT CHARACTER SET utf8, thumbId INTEGER, UNIQUE(identifier(333))); CALL create_index_if_not_exists('CustomIdentifiers','id_customIdentifiers','thumbId'); ALTER TABLE Settings RENAME ThumbSettings; ALTER TABLE ThumbSettings MODIFY COLUMN keyword VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci, MODIFY COLUMN value LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE ThumbSettings ENGINE InnoDB; ALTER TABLE Thumbnails ENGINE InnoDB; ALTER TABLE UniqueHashes ENGINE InnoDB; ALTER TABLE FilePaths MODIFY COLUMN path VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE FilePaths ENGINE InnoDB; ALTER TABLE CustomIdentifiers MODIFY COLUMN identifier VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER TABLE CustomIdentifiers ENGINE InnoDB; ALTER TABLE UniqueHashes ADD CONSTRAINT UniqueHashes_Thumbnails FOREIGN KEY (thumbId) REFERENCES Thumbnails (id) ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE FilePaths ADD CONSTRAINT FilePaths_Thumbnails FOREIGN KEY (thumbId) REFERENCES Thumbnails (id) ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE CustomIdentifiers ADD CONSTRAINT CustomIdentifiers_Thumbnails FOREIGN KEY (thumbId) REFERENCES Thumbnails (id) ON DELETE CASCADE ON UPDATE CASCADE; OPTIMIZE TABLE Albums, Images, ImageInformation, ImageMetadata, VideoMetadata, ImagePositions, ImageComments, ImageCopyright, ImageProperties, ImageHistory, ImageRelations, Tags, ImageTags, ImageTagProperties; OPTIMIZE TABLE Thumbnails, UniqueHashes, FilePaths, CustomIdentifiers; OPTIMIZE TABLE Identities, IdentityAttributes; OPTIMIZE TABLE ImageSimilarity, ImageHaarMatrix, SimilaritySettings; CHECK TABLE Albums, Images, ImageInformation, ImageMetadata, VideoMetadata, ImagePositions, ImageComments, ImageCopyright, ImageProperties, ImageHistory, ImageRelations, Tags, ImageTags, ImageTagProperties; CHECK TABLE Thumbnails, UniqueHashes, FilePaths, CustomIdentifiers; CHECK TABLE Identities, IdentityAttributes; CHECK TABLE ImageSimilarity, ImageHaarMatrix, SimilaritySettings; diff --git a/core/libs/database/collection/collectionmanager.cpp b/core/libs/database/collection/collectionmanager.cpp index 46bb787399..945186d3d8 100644 --- a/core/libs/database/collection/collectionmanager.cpp +++ b/core/libs/database/collection/collectionmanager.cpp @@ -1,1803 +1,1803 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2007-04-09 * Description : Collection location management * * Copyright (C) 2007-2009 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 "collectionmanager.h" // Qt includes #include #include #include #include #include // KDE includes #include #include #include #include #include #include #include #include #include // Local includes #include "digikam_debug.h" #include "coredbaccess.h" #include "coredbchangesets.h" #include "coredbtransaction.h" #include "coredb.h" #include "collectionscanner.h" #include "collectionlocation.h" namespace Digikam { class AlbumRootLocation : public CollectionLocation { public: AlbumRootLocation() : available(false), hidden(false) { } explicit AlbumRootLocation(const AlbumRootInfo& info) { qCDebug(DIGIKAM_DATABASE_LOG) << "Creating new Location " << info.specificPath << " uuid " << info.identifier; m_id = info.id; m_type = (Type)info.type; specificPath = info.specificPath; identifier = info.identifier; m_label = info.label; m_path.clear(); setStatus((CollectionLocation::Status)info.status); } void setStatusFromFlags() { if (hidden) { m_status = CollectionLocation::LocationHidden; } else { if (available) { m_status = CollectionLocation::LocationAvailable; } else { m_status = CollectionLocation::LocationUnavailable; } } } void setStatus(CollectionLocation::Status s) { m_status = s; // status is exclusive, and Hidden wins // but really both states are independent // - a hidden location might or might not be available if (m_status == CollectionLocation::LocationAvailable) { available = true; hidden = false; } else if (m_status == CollectionLocation::LocationHidden) { available = false; hidden = true; } else // Unavailable, Null, Deleted { available = false; hidden = false; } } void setId(int id) { m_id = id; } void setAbsolutePath(const QString& path) { m_path = path; } void setType(Type type) { m_type = type; } void setLabel(const QString& label) { m_label = label; } public: QString identifier; QString specificPath; bool available; bool hidden; }; // ------------------------------------------------- class SolidVolumeInfo { public: SolidVolumeInfo() : isRemovable(false), isOpticalDisc(false), isMounted(false) { } bool isNull() const { return path.isNull(); } public: QString udi; // Solid device UDI of the StorageAccess device QString path; // mount path of volume, with trailing slash QString uuid; // UUID as from Solid QString label; // volume label (think of CDs) bool isRemovable; // may be removed bool isOpticalDisc; // is an optical disk device as CD/DVD/BR bool isMounted; // is mounted on File System. }; // ------------------------------------------------- class CollectionManagerPrivate { public: explicit CollectionManagerPrivate(CollectionManager* s); // hack for Solid's threading problems QList actuallyListVolumes(); void slotTriggerUpdateVolumesList(); QList volumesListCache; /// Access Solid and return a list of storage volumes QList listVolumes(); /** * Find from a given list (usually the result of listVolumes) the volume * corresponding to the location */ SolidVolumeInfo findVolumeForLocation(const AlbumRootLocation* location, const QList volumes); /** * Find from a given list (usually the result of listVolumes) the volume * on which the file path specified by the url is located. */ SolidVolumeInfo findVolumeForUrl(const QUrl& fileUrl, const QList volumes); /// Create the volume identifier for the given volume info static QString volumeIdentifier(const SolidVolumeInfo& info); /// Create a volume identifier based on the path only QString volumeIdentifier(const QString& path); /// Create a network share identifier based on the mountpath QString networkShareIdentifier(const QString& path); /// Return the path, if location has a path-only identifier. Else returns a null string. QString pathFromIdentifier(const AlbumRootLocation* location); /// Return the path, if location has a path-only identifier. Else returns a null string. QStringList networkShareMountPathsFromIdentifier(const AlbumRootLocation* location); /// Create an MD5 hash of the top-level entries (file names, not file content) of the given path static QString directoryHash(const QString& path); /// Check if a location for specified path exists, assuming the given list of locations was deleted bool checkIfExists(const QString& path, QList assumeDeleted); /// Make a user presentable description, regardless of current location status QString technicalDescription(const AlbumRootLocation* location); public: QMap locations; bool changingDB; QStringList udisToWatch; bool watchEnabled; CollectionManager* s; }; // ------------------------------------------------- class ChangingDB { public: explicit ChangingDB(CollectionManagerPrivate* d) : d(d) { d->changingDB = true; } ~ChangingDB() { d->changingDB = false; } public: CollectionManagerPrivate* const d; }; } // namespace Digikam // This is because of the private slot; we'd want a collectionmanager_p.h #include "collectionmanager.h" // krazy:exclude=includes namespace Digikam { CollectionManagerPrivate::CollectionManagerPrivate(CollectionManager* s) : changingDB(false), watchEnabled(false), s(s) { QObject::connect(s, SIGNAL(triggerUpdateVolumesList()), s, SLOT(slotTriggerUpdateVolumesList()), Qt::BlockingQueuedConnection); } QList CollectionManagerPrivate::listVolumes() { // Move calls to Solid to the main thread. // Solid was meant to be thread-safe, but it is not (KDE4.0), // calling from a non-UI thread leads to a reversible // lock-up of variable length. if (QThread::currentThread() == QCoreApplication::instance()->thread()) { return actuallyListVolumes(); } else { // emit a blocking queued signal to move call to main thread emit(s->triggerUpdateVolumesList()); return volumesListCache; } } void CollectionManagerPrivate::slotTriggerUpdateVolumesList() { volumesListCache = actuallyListVolumes(); } QList CollectionManagerPrivate::actuallyListVolumes() { QList volumes; //qCDebug(DIGIKAM_DATABASE_LOG) << "listFromType"; QList devices = Solid::Device::listFromType(Solid::DeviceInterface::StorageAccess); //qCDebug(DIGIKAM_DATABASE_LOG) << "got listFromType"; udisToWatch.clear(); foreach(const Solid::Device& accessDevice, devices) { // check for StorageAccess if (!accessDevice.is()) { continue; } // mark as a device of principal interest udisToWatch << accessDevice.udi(); const Solid::StorageAccess* access = accessDevice.as(); // watch mount status (remove previous connections) QObject::disconnect(access, SIGNAL(accessibilityChanged(bool,QString)), s, SLOT(accessibilityChanged(bool,QString))); QObject::connect(access, SIGNAL(accessibilityChanged(bool,QString)), s, SLOT(accessibilityChanged(bool,QString))); if (!access->isAccessible()) { continue; } // check for StorageDrive Solid::Device driveDevice; for (Solid::Device currentDevice = accessDevice; currentDevice.isValid(); currentDevice = currentDevice.parent()) { if (currentDevice.is()) { driveDevice = currentDevice; break; } } /* * We cannot require a drive, some logical volumes may not have "one" drive as parent * See bug 273369 if (!driveDevice.isValid()) { continue; } */ Solid::StorageDrive* drive = driveDevice.as(); // check for StorageVolume Solid::Device volumeDevice; for (Solid::Device currentDevice = accessDevice; currentDevice.isValid(); currentDevice = currentDevice.parent()) { if (currentDevice.is()) { volumeDevice = currentDevice; break; } } if (!volumeDevice.isValid()) { continue; } Solid::StorageVolume* const volume = volumeDevice.as(); SolidVolumeInfo info; info.udi = accessDevice.udi(); info.path = QDir::fromNativeSeparators(access->filePath()); info.isMounted = access->isAccessible(); if (!info.path.isEmpty() && !info.path.endsWith(QLatin1Char('/'))) { info.path += QLatin1Char('/'); } info.uuid = volume->uuid(); info.label = volume->label(); if (drive) { info.isRemovable = drive->isHotpluggable() || drive->isRemovable(); } else { // impossible to know, but probably not hotpluggable (see comment above) info.isRemovable = false; } info.isOpticalDisc = volumeDevice.is(); volumes << info; } // This is the central place where the watch is enabled watchEnabled = true; return volumes; } QString CollectionManagerPrivate::volumeIdentifier(const SolidVolumeInfo& volume) { QUrl url; url.setScheme(QLatin1String("volumeid")); // On changing these, please update the checkLocation() code bool identifyByUUID = !volume.uuid.isEmpty(); bool identifyByLabel = !identifyByUUID && !volume.label.isEmpty() && (volume.isOpticalDisc || volume.isRemovable); bool addDirectoryHash = identifyByLabel && volume.isOpticalDisc; bool identifyByMountPath = !identifyByUUID && !identifyByLabel; if (identifyByUUID) { QUrlQuery q(url); q.addQueryItem(QLatin1String("uuid"), volume.uuid); url.setQuery(q); } if (identifyByLabel) { QUrlQuery q(url); q.addQueryItem(QLatin1String("label"), volume.label); url.setQuery(q); } if (addDirectoryHash) { // for CDs, we store a hash of the root directory. May be useful. QString dirHash = directoryHash(volume.path); if (!dirHash.isNull()) { QUrlQuery q(url); q.addQueryItem(QLatin1String("directoryhash"), dirHash); url.setQuery(q); } } if (identifyByMountPath) { QUrlQuery q(url); q.addQueryItem(QLatin1String("mountpath"), volume.path); url.setQuery(q); } return url.url(); } QString CollectionManagerPrivate::volumeIdentifier(const QString& path) { QUrl url; url.setScheme(QLatin1String("volumeid")); QUrlQuery q(url); q.addQueryItem(QLatin1String("path"), path); url.setQuery(q); return url.url(); } QString CollectionManagerPrivate::networkShareIdentifier(const QString& path) { QUrl url; url.setScheme(QLatin1String("networkshareid")); QUrlQuery q(url); q.addQueryItem(QLatin1String("mountpath"), path); url.setQuery(q); return url.url(); } QString CollectionManagerPrivate::pathFromIdentifier(const AlbumRootLocation* location) { QUrl url(location->identifier); if (url.scheme() != QLatin1String("volumeid")) { return QString(); } return QUrlQuery(url).queryItemValue(QLatin1String("path")); } QStringList CollectionManagerPrivate::networkShareMountPathsFromIdentifier(const AlbumRootLocation* location) { // using a QUrl because QUrl cannot handle duplicate query items QUrl url(location->identifier); if (url.scheme() != QLatin1String("networkshareid")) { return QStringList(); } return QUrlQuery(url).allQueryItemValues(QLatin1String("mountpath")); } QString CollectionManagerPrivate::directoryHash(const QString& path) { QDir dir(path); if (dir.isReadable()) { QStringList entries = dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); QCryptographicHash md5(QCryptographicHash::Md5); foreach(const QString& entry, entries) { md5.addData(entry.toUtf8()); } return QString::fromUtf8(md5.result().toHex()); } return QString(); } SolidVolumeInfo CollectionManagerPrivate::findVolumeForLocation(const AlbumRootLocation* location, const QList volumes) { QUrl url(location->identifier); QString queryItem; if (url.scheme() != QLatin1String("volumeid")) { return SolidVolumeInfo(); } if (!(queryItem = QUrlQuery(url).queryItemValue(QLatin1String("uuid"))).isNull()) { foreach(const SolidVolumeInfo& volume, volumes) { if (volume.uuid.compare(queryItem, Qt::CaseInsensitive) == 0) { return volume; } } return SolidVolumeInfo(); } else if (!(queryItem = QUrlQuery(url).queryItemValue(QLatin1String("label"))).isNull()) { // This one is a bit more difficult, as we take into account the possibility // that the label is not unique, and we take some care to make it work anyway. // find all available volumes with the given label (usually one) QList candidateVolumes; foreach(const SolidVolumeInfo& volume, volumes) { if (volume.label == queryItem) { candidateVolumes << volume; } } if (candidateVolumes.isEmpty()) { return SolidVolumeInfo(); } // find out of there is another location with the same label (usually not) bool hasOtherLocation = false; foreach(AlbumRootLocation* const otherLocation, locations) { if (otherLocation == location) { continue; } QUrl otherUrl(otherLocation->identifier); if (otherUrl.scheme() == QLatin1String("volumeid") && QUrlQuery(otherUrl).queryItemValue(QLatin1String("label")) == queryItem) { hasOtherLocation = true; break; } } // the usual, easy case if (candidateVolumes.size() == 1 && !hasOtherLocation) { return candidateVolumes.first(); } else { // not unique: try to use the directoryhash QString dirHash = QUrlQuery(url).queryItemValue(QLatin1String("directoryhash")); // bail out if not provided if (dirHash.isNull()) { qCDebug(DIGIKAM_DATABASE_LOG) << "No directory hash specified for the non-unique Label" << queryItem << "Resorting to returning the first match."; return candidateVolumes.first(); } // match against directory hash foreach(const SolidVolumeInfo& volume, candidateVolumes) { QString volumeDirHash = directoryHash(volume.path); if (volumeDirHash == dirHash) { return volume; } } } return SolidVolumeInfo(); } else if (!(queryItem = QUrlQuery(url).queryItemValue(QLatin1String("mountpath"))).isNull()) { foreach(const SolidVolumeInfo& volume, volumes) { if (volume.isMounted && volume.path == queryItem) { return volume; } } return SolidVolumeInfo(); } return SolidVolumeInfo(); } QString CollectionManagerPrivate::technicalDescription(const AlbumRootLocation* albumLoc) { QUrl url(albumLoc->identifier); QString queryItem; if (url.scheme() == QLatin1String("volumeid")) { if (!(queryItem = QUrlQuery(url).queryItemValue(QLatin1String("uuid"))).isNull()) { return i18nc("\"relative path\" on harddisk partition with \"UUID\"", "Folder \"%1\" on the volume with the id \"%2\"", QDir::toNativeSeparators(albumLoc->specificPath), queryItem); } else if (!(queryItem = QUrlQuery(url).queryItemValue(QLatin1String("label"))).isNull()) { return i18nc("\"relative path\" on harddisk partition with \"label\"", "Folder \"%1\" on the volume labeled \"%2\"", QDir::toNativeSeparators(albumLoc->specificPath), queryItem); } else if (!(queryItem = QUrlQuery(url).queryItemValue(QLatin1String("mountpath"))).isNull()) { return QString::fromUtf8("\"%1\"").arg(queryItem); } } else if (url.scheme() == QLatin1String("networkshareid")) { if (!(queryItem = QUrlQuery(url).queryItemValue(QLatin1String("mountpath"))).isNull()) { return i18nc("@info", "Shared directory mounted at %1", QDir::toNativeSeparators(queryItem)); } } return QString(); } SolidVolumeInfo CollectionManagerPrivate::findVolumeForUrl(const QUrl& fileUrl, const QList volumes) { SolidVolumeInfo volume; // v.path is specified to have a trailing slash. path needs one as well. - QString path = fileUrl.toLocalFile() + QLatin1String("/"); + QString path = fileUrl.toLocalFile() + QLatin1Char('/'); int volumeMatch = 0; //FIXME: Network shares! Here we get only the volume of the mount path... // This is probably not really clean. But Solid does not help us. foreach(const SolidVolumeInfo& v, volumes) { if (v.isMounted && !v.path.isEmpty() && path.startsWith(v.path)) { int length = v.path.length(); if (length > volumeMatch) { volumeMatch = v.path.length(); volume = v; } } } if (!volumeMatch) { qCDebug(DIGIKAM_DATABASE_LOG) << "Failed to detect a storage volume for path " << path << " with Solid"; } return volume; } bool CollectionManagerPrivate::checkIfExists(const QString& filePath, QList assumeDeleted) { const QUrl filePathUrl = QUrl::fromLocalFile(filePath); CoreDbAccess access; foreach(AlbumRootLocation* const location, locations) { const QUrl locationPathUrl = QUrl::fromLocalFile(location->albumRootPath()); //qCDebug(DIGIKAM_DATABASE_LOG) << filePathUrl << locationPathUrl; // make sure filePathUrl is neither a child nor a parent // of an existing collection if (!locationPathUrl.isEmpty() && (filePathUrl.isParentOf(locationPathUrl) || locationPathUrl.isParentOf(filePathUrl)) ) { bool isDeleted = false; foreach(const CollectionLocation& deletedLoc, assumeDeleted) { if (deletedLoc.id() == location->id()) { isDeleted = true; break; } } if (!isDeleted) { return true; } } } return false; } // ------------------------------------------------- CollectionManager* CollectionManager::m_instance = 0; CollectionManager* CollectionManager::instance() { if (!m_instance) { m_instance = new CollectionManager; } return m_instance; } void CollectionManager::cleanUp() { delete m_instance; m_instance = 0; } CollectionManager::CollectionManager() : d(new CollectionManagerPrivate(this)) { qRegisterMetaType("CollectionLocation"); connect(Solid::DeviceNotifier::instance(), SIGNAL(deviceAdded(QString)), this, SLOT(deviceAdded(QString))); connect(Solid::DeviceNotifier::instance(), SIGNAL(deviceRemoved(QString)), this, SLOT(deviceRemoved(QString))); // CoreDbWatch slot is connected at construction of CoreDbWatch, which may be later. } CollectionManager::~CollectionManager() { qDeleteAll(d->locations.values()); delete d; } void CollectionManager::refresh() { { // if called from the CoreDbAccess constructor itself, it will // hold a flag to prevent endless recursion CoreDbAccess access; clear_locked(); } updateLocations(); } void CollectionManager::setWatchDisabled() { d->watchEnabled = false; } CollectionLocation CollectionManager::addLocation(const QUrl& fileUrl, const QString& label) { qCDebug(DIGIKAM_DATABASE_LOG) << "addLocation " << fileUrl; QString path = fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile(); if (!locationForPath(path).isNull()) { return CollectionLocation(); } QList volumes = d->listVolumes(); SolidVolumeInfo volume = d->findVolumeForUrl(fileUrl, volumes); if (!volume.isNull()) { CoreDbAccess access; // volume.path has a trailing slash. We want to split in front of this. QString specificPath = path.mid(volume.path.length() - 1); AlbumRoot::Type type; if (volume.isRemovable) { type = AlbumRoot::VolumeRemovable; } else { type = AlbumRoot::VolumeHardWired; } ChangingDB changing(d); access.db()->addAlbumRoot(type, d->volumeIdentifier(volume), specificPath, label); } else { // Empty volumes indicates that Solid is not working correctly. if (volumes.isEmpty()) { qCDebug(DIGIKAM_DATABASE_LOG) << "Solid did not return any storage volumes on your system."; qCDebug(DIGIKAM_DATABASE_LOG) << "This indicates a missing implementation or a problem with your installation"; qCDebug(DIGIKAM_DATABASE_LOG) << "On Linux, check that Solid and HAL are working correctly." "Problems with RAID partitions have been reported, if you have RAID this error may be normal."; qCDebug(DIGIKAM_DATABASE_LOG) << "On Windows, Solid may not be fully implemented, if you are running Windows this error may be normal."; } // fall back qCWarning(DIGIKAM_DATABASE_LOG) << "Unable to identify a path with Solid. Adding the location with path only."; ChangingDB changing(d); CoreDbAccess().db()->addAlbumRoot(AlbumRoot::VolumeHardWired, d->volumeIdentifier(path), QLatin1String("/"), label); } // Do not emit the locationAdded signal here, it is done in updateLocations() updateLocations(); return locationForPath(path); } CollectionLocation CollectionManager::addNetworkLocation(const QUrl& fileUrl, const QString& label) { qCDebug(DIGIKAM_DATABASE_LOG) << "addLocation " << fileUrl; QString path = fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile(); if (!locationForPath(path).isNull()) { return CollectionLocation(); } ChangingDB changing(d); CoreDbAccess().db()->addAlbumRoot(AlbumRoot::Network, d->networkShareIdentifier(path), QLatin1String("/"), label); // Do not emit the locationAdded signal here, it is done in updateLocations() updateLocations(); return locationForPath(path); } CollectionManager::LocationCheckResult CollectionManager::checkLocation(const QUrl& fileUrl, QList assumeDeleted, QString* message, QString* iconName) { if (!fileUrl.isLocalFile()) { if (message) { *message = i18n("Sorry, digiKam does not support remote URLs as collections."); } if (iconName) { *iconName = QLatin1String("dialog-error"); } return LocationNotAllowed; } QString path = fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile(); QDir dir(path); if (!dir.isReadable()) { if (message) { *message = i18n("The selected folder does not exist or is not readable"); } if (iconName) { *iconName = QLatin1String("dialog-error"); } return LocationNotAllowed; } if (d->checkIfExists(path, assumeDeleted)) { if (message) { *message = i18n("There is already a collection containing the folder \"%1\"", QDir::toNativeSeparators(path)); } if (iconName) { *iconName = QLatin1String("dialog-error"); } return LocationNotAllowed; } QList volumes = d->listVolumes(); SolidVolumeInfo volume = d->findVolumeForUrl(fileUrl, volumes); if (!volume.isNull()) { if (!volume.uuid.isEmpty()) { if (volume.isRemovable) { if (message) { *message = i18n("The storage media can be uniquely identified."); } if (iconName) { *iconName = QLatin1String("drive-removable-media"); } } else { if (message) { *message = i18n("The collection is located on your harddisk"); } if (iconName) { *iconName = QLatin1String("drive-harddisk"); } } return LocationAllRight; } else if (!volume.label.isEmpty() && (volume.isOpticalDisc || volume.isRemovable)) { if (volume.isOpticalDisc) { bool hasOtherLocation = false; foreach(AlbumRootLocation* const otherLocation, d->locations) { QUrl otherUrl(otherLocation->identifier); if (otherUrl.scheme() == QLatin1String("volumeid") && QUrlQuery(otherUrl).queryItemValue(QLatin1String("label")) == volume.label) { hasOtherLocation = true; break; } } if (iconName) { *iconName = QLatin1String("media-optical"); } if (hasOtherLocation) { if (message) *message = i18n("This is a CD/DVD, which is identified by the label " "that you can set in your CD burning application. " "There is already another entry with the same label. " "The two will be distinguished by the files in the top directory, " "so please do not append files to the CD, or it will not be recognized. " "In the future, please set a unique label on your CDs and DVDs " "if you intend to use them with digiKam."); return LocationHasProblems; } else { if (message) *message = i18n("This is a CD/DVD. It will be identified by the label (\"%1\")" "that you have set in your CD burning application. " "If you create further CDs for use with digikam in the future, " "please remember to give them a unique label as well.", volume.label); return LocationAllRight; } } else { // Which situation? HasProblems or AllRight? if (message) *message = i18n("This is a removable storage medium that will be identified by its label (\"%1\")", volume.label); if (iconName) { *iconName = QLatin1String("drive-removable-media"); } return LocationAllRight; } } else { if (message) *message = i18n("This entry will only be identified by the path where it is found on your system (\"%1\"). " "No more specific means of identification (UUID, label) is available.", QDir::toNativeSeparators(volume.path)); if (iconName) { *iconName = QLatin1String("drive-removale-media"); } return LocationHasProblems; } } else { if (message) *message = i18n("It is not possible on your system to identify the storage medium of this path. " "It will be added using the file path as the only identifier. " "This will work well for your local hard disk."); if (iconName) { *iconName = QLatin1String("folder-important"); } return LocationHasProblems; } } CollectionManager::LocationCheckResult CollectionManager::checkNetworkLocation(const QUrl& fileUrl, QList assumeDeleted, QString* message, QString* iconName) { if (!fileUrl.isLocalFile()) { if (message) { if (fileUrl.scheme() == QLatin1String("smb")) *message = i18n("You need to locally mount your Samba share. " "Sorry, digiKam does currently not support smb:// URLs. "); else *message = i18n("Your network storage must be set up to be accessible " "as files and folders through the operating system. " "DigiKam does not support remote URLs."); } if (iconName) { *iconName = QLatin1String("dialog-error"); } return LocationNotAllowed; } QString path = fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile(); QDir dir(path); if (!dir.isReadable()) { if (message) { *message = i18n("The selected folder does not exist or is not readable"); } if (iconName) { *iconName = QLatin1String("dialog-error"); } return LocationNotAllowed; } if (d->checkIfExists(path, assumeDeleted)) { if (message) { *message = i18n("There is already a collection for a network share with the same path."); } if (iconName) { *iconName = QLatin1String("dialog-error"); } return LocationNotAllowed; } if (message) *message = i18n("The network share will be identified by the path you selected. " "If the path is empty, the share will be considered unavailable."); if (iconName) { *iconName = QLatin1String("network-wired-activated"); } return LocationAllRight; } void CollectionManager::removeLocation(const CollectionLocation& location) { { CoreDbAccess access; AlbumRootLocation* const albumLoc = d->locations.value(location.id()); if (!albumLoc) { return; } // Ensure that all albums are set to orphan and no images will be permanently deleted, // as would do only calling deleteAlbumRoot by a Trigger QList albumIds = access.db()->getAlbumsOnAlbumRoot(albumLoc->id()); ChangingDB changing(d); CollectionScanner scanner; CoreDbTransaction transaction(&access); scanner.safelyRemoveAlbums(albumIds); access.db()->deleteAlbumRoot(albumLoc->id()); } // Do not emit the locationRemoved signal here, it is done in updateLocations() updateLocations(); } QList CollectionManager::checkHardWiredLocations() { QList disappearedLocations; QList volumes = d->listVolumes(); CoreDbAccess access; foreach(AlbumRootLocation* const location, d->locations) { // Hardwired and unavailable? if (location->type() == CollectionLocation::TypeVolumeHardWired && location->status() == CollectionLocation::LocationUnavailable) { disappearedLocations << *location; } } return disappearedLocations; } void CollectionManager::migrationCandidates(const CollectionLocation& location, QString* const description, QStringList* const candidateIdentifiers, QStringList* const candidateDescriptions) { description->clear(); candidateIdentifiers->clear(); candidateDescriptions->clear(); QList volumes = d->listVolumes(); CoreDbAccess access; AlbumRootLocation* const albumLoc = d->locations.value(location.id()); if (!albumLoc) { return; } *description = d->technicalDescription(albumLoc); // Find possible new volumes where the specific path is found. foreach(const SolidVolumeInfo& info, volumes) { if (info.isMounted && !info.path.isEmpty()) { QDir dir(info.path + albumLoc->specificPath); if (dir.exists()) { *candidateIdentifiers << d->volumeIdentifier(info); *candidateDescriptions << dir.absolutePath(); } } } } void CollectionManager::migrateToVolume(const CollectionLocation& location, const QString& identifier) { CoreDbAccess access; AlbumRootLocation* const albumLoc = d->locations.value(location.id()); if (!albumLoc) { return; } // update db ChangingDB db(d); access.db()->migrateAlbumRoot(albumLoc->id(), identifier); albumLoc->identifier = identifier; updateLocations(); } void CollectionManager::setLabel(const CollectionLocation& location, const QString& label) { CoreDbAccess access; AlbumRootLocation* const albumLoc = d->locations.value(location.id()); if (!albumLoc) { return; } // update db ChangingDB db(d); access.db()->setAlbumRootLabel(albumLoc->id(), label); // update local structure albumLoc->setLabel(label); emit locationPropertiesChanged(*albumLoc); } void CollectionManager::changeType(const CollectionLocation& location, int type) { CoreDbAccess access; AlbumRootLocation* const albumLoc = d->locations.value(location.id()); if (!albumLoc) { return; } // update db ChangingDB db(d); access.db()->changeAlbumRootType(albumLoc->id(), (AlbumRoot::Type)type); // update local structure albumLoc->setType((CollectionLocation::Type)type); emit locationPropertiesChanged(*albumLoc); } QList CollectionManager::allLocations() { CoreDbAccess access; QList list; foreach(AlbumRootLocation* const location, d->locations) { list << *location; } return list; } QList CollectionManager::allAvailableLocations() { CoreDbAccess access; QList list; foreach(AlbumRootLocation* const location, d->locations) { if (location->status() == CollectionLocation::LocationAvailable) { list << *location; } } return list; } QStringList CollectionManager::allAvailableAlbumRootPaths() { CoreDbAccess access; QStringList list; foreach(AlbumRootLocation* const location, d->locations) { if (location->status() == CollectionLocation::LocationAvailable) { list << location->albumRootPath(); } } return list; } CollectionLocation CollectionManager::locationForAlbumRootId(int id) { CoreDbAccess access; AlbumRootLocation* const location = d->locations.value(id); if (location) { return *location; } else { return CollectionLocation(); } } CollectionLocation CollectionManager::locationForAlbumRoot(const QUrl& fileUrl) { return locationForAlbumRootPath(fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile()); } CollectionLocation CollectionManager::locationForAlbumRootPath(const QString& albumRootPath) { if (!QFileInfo::exists(albumRootPath)) { qCWarning(DIGIKAM_DATABASE_LOG) << "Album root path not exist" << albumRootPath; qCWarning(DIGIKAM_DATABASE_LOG) << "Drive or network connection broken?"; updateLocations(); } CoreDbAccess access; foreach(AlbumRootLocation* const location, d->locations) { if (location->albumRootPath() == albumRootPath) { return *location; } } return CollectionLocation(); } CollectionLocation CollectionManager::locationForUrl(const QUrl& fileUrl) { return locationForPath(fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile()); } CollectionLocation CollectionManager::locationForPath(const QString& givenPath) { CoreDbAccess access; foreach(AlbumRootLocation* const location, d->locations) { QString rootPath = location->albumRootPath(); QString filePath = QDir::fromNativeSeparators(givenPath); if (!rootPath.isEmpty() && filePath.startsWith(rootPath)) { // see also bug #221155 for extra checks if (filePath == rootPath || filePath.startsWith(rootPath + QLatin1Char('/'))) { return *location; } } } return CollectionLocation(); } QString CollectionManager::albumRootPath(int id) { CoreDbAccess access; CollectionLocation* const location = d->locations.value(id); if (location && location->status() == CollectionLocation::LocationAvailable) { return location->albumRootPath(); } return QString(); } QString CollectionManager::albumRootLabel(int id) { CoreDbAccess access; CollectionLocation* const location = d->locations.value(id); if (location && location->status() == CollectionLocation::LocationAvailable) { return location->label(); } return QString(); } QUrl CollectionManager::albumRoot(const QUrl& fileUrl) { return QUrl::fromLocalFile(albumRootPath(fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile())); } QString CollectionManager::albumRootPath(const QUrl& fileUrl) { return albumRootPath(fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile()); } QString CollectionManager::albumRootPath(const QString& givenPath) { CoreDbAccess access; foreach(AlbumRootLocation* const location, d->locations) { QString rootPath = location->albumRootPath(); QString filePath = QDir::fromNativeSeparators(givenPath); if (!rootPath.isEmpty() && filePath.startsWith(rootPath)) { // see also bug #221155 for extra checks if (filePath == rootPath || filePath.startsWith(rootPath + QLatin1Char('/'))) { return location->albumRootPath(); } } } return QString(); } bool CollectionManager::isAlbumRoot(const QUrl& fileUrl) { return isAlbumRoot(fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile()); } bool CollectionManager::isAlbumRoot(const QString& filePath) { CoreDbAccess access; foreach(AlbumRootLocation* const location, d->locations) { if (filePath == location->albumRootPath()) { return true; } } return false; } QString CollectionManager::album(const QUrl& fileUrl) { return album(fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile()); } QString CollectionManager::album(const QString& filePath) { CoreDbAccess access; foreach(AlbumRootLocation* const location, d->locations) { QString absolutePath = location->albumRootPath(); if (absolutePath.isEmpty()) { continue; } QString firstPart = filePath.left(absolutePath.length()); if (firstPart == absolutePath) { if (filePath == absolutePath || (filePath.length() == absolutePath.length() + 1 && filePath.right(1) == QLatin1String("/"))) { return QLatin1String("/"); } else { QString album = filePath.mid(absolutePath.length()); if (album.endsWith(QLatin1Char('/'))) { album.chop(1); } return album; } } } return QString(); } QString CollectionManager::album(const CollectionLocation& location, const QUrl& fileUrl) { return album(location, fileUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile()); } QString CollectionManager::album(const CollectionLocation& location, const QString& filePath) { if (location.isNull()) { return QString(); } QString absolutePath = location.albumRootPath(); if (filePath == absolutePath) { return QLatin1String("/"); } else { QString album = filePath.mid(absolutePath.length()); if (album.endsWith(QLatin1Char('/'))) { album.chop(1); } return album; } } QUrl CollectionManager::oneAlbumRoot() { return QUrl::fromLocalFile(oneAlbumRootPath()); } QString CollectionManager::oneAlbumRootPath() { CoreDbAccess access; foreach(AlbumRootLocation* const location, d->locations) { if (location->status() == CollectionLocation::LocationAvailable) { return location->albumRootPath(); } } return QString(); } void CollectionManager::deviceAdded(const QString& udi) { if (!d->watchEnabled) { return; } Solid::Device device(udi); if (device.is()) { updateLocations(); } } void CollectionManager::deviceRemoved(const QString& udi) { if (!d->watchEnabled) { return; } // we can't access the Solid::Device to check because it is removed CoreDbAccess access; if (!d->udisToWatch.contains(udi)) { return; } updateLocations(); } void CollectionManager::accessibilityChanged(bool accessible, const QString& udi) { Q_UNUSED(accessible); Q_UNUSED(udi); updateLocations(); } void CollectionManager::updateLocations() { // get information from Solid QList volumes; { // Absolutely ensure that the db mutex is not held when emitting the blocking queued signal! Deadlock! CoreDbAccessUnlock unlock; volumes = d->listVolumes(); } { CoreDbAccess access; // read information from database QList infos = access.db()->getAlbumRoots(); // synchronize map with database QMap locs = d->locations; d->locations.clear(); foreach(const AlbumRootInfo& info, infos) { if (locs.contains(info.id)) { d->locations[info.id] = locs.value(info.id); locs.remove(info.id); } else { d->locations[info.id] = new AlbumRootLocation(info); } } // delete old locations foreach(AlbumRootLocation* const location, locs) { CollectionLocation::Status oldStatus = location->status(); location->setStatus(CollectionLocation::LocationDeleted); emit locationStatusChanged(*location, oldStatus); delete location; } // update status with current access state, store old status in list QList oldStatus; foreach(AlbumRootLocation* const location, d->locations) { oldStatus << location->status(); bool available = false; QString absolutePath; if (location->type() == CollectionLocation::TypeNetwork) { foreach(const QString& path, d->networkShareMountPathsFromIdentifier(location)) { QDir dir(path); available = dir.isReadable() && dir.entryList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot).count() > 0; absolutePath = path; if (available) { break; } } } else { SolidVolumeInfo info = d->findVolumeForLocation(location, volumes); if (!info.isNull()) { available = info.isMounted; QString volumePath = info.path; // volume.path has a trailing slash (and this is good) // but specific path has a leading slash, so remove it volumePath.chop(1); // volumePath is the mount point of the volume; // specific path is the path on the file system of the volume. absolutePath = volumePath + location->specificPath; } else { QString path = d->pathFromIdentifier(location); if (!path.isNull()) { available = true; // Here we have the absolute path as definition of the volume. // specificPath is "/" as per convention, but ignored, // absolute path shall not have a trailing slash. absolutePath = path; } } } // set values in location // Don't touch location->status, do not interfere with "hidden" setting location->available = available; location->setAbsolutePath(absolutePath); qCDebug(DIGIKAM_DATABASE_LOG) << "location for " << absolutePath << " is available " << available; // set the status depending on "hidden" and "available" location->setStatusFromFlags(); } // emit status changes (and new locations) int i = 0; foreach(AlbumRootLocation* const location, d->locations) { if (oldStatus.at(i) != location->status()) { emit locationStatusChanged(*location, oldStatus.at(i)); } ++i; } } } void CollectionManager::clear_locked() { // Internal method: Called with mutex locked // Cave: Difficult recursions with CoreDbAccess constructor and setParameters foreach(AlbumRootLocation* const location, d->locations) { CollectionLocation::Status oldStatus = location->status(); location->setStatus(CollectionLocation::LocationDeleted); emit locationStatusChanged(*location, oldStatus); delete location; } d->locations.clear(); } void CollectionManager::slotAlbumRootChange(const AlbumRootChangeset& changeset) { if (d->changingDB) { return; } switch (changeset.operation()) { case AlbumRootChangeset::Added: case AlbumRootChangeset::Deleted: updateLocations(); break; case AlbumRootChangeset::PropertiesChanged: // label has changed { CollectionLocation toBeEmitted; { CoreDbAccess access; AlbumRootLocation* const location = d->locations.value(changeset.albumRootId()); if (location) { QList infos = access.db()->getAlbumRoots(); foreach(const AlbumRootInfo& info, infos) { if (info.id == location->id()) { location->setLabel(info.label); toBeEmitted = *location; break; } } } } if (!toBeEmitted.isNull()) { emit locationPropertiesChanged(toBeEmitted); } } break; case AlbumRootChangeset::Unknown: break; } } } // namespace Digikam #include "moc_collectionmanager.cpp" diff --git a/core/libs/database/coredb/coredbschemaupdater.cpp b/core/libs/database/coredb/coredbschemaupdater.cpp index 7cf3139e2b..52843ee6da 100644 --- a/core/libs/database/coredb/coredbschemaupdater.cpp +++ b/core/libs/database/coredb/coredbschemaupdater.cpp @@ -1,1580 +1,1583 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2007-04-16 * Description : Core database Schema updater * * Copyright (C) 2007-2012 by Marcel Wiesweg * Copyright (C) 2009-2018 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 "coredbschemaupdater.h" // Qt includes #include #include #include #include #include // KDE includes #include #include // Local includes #include "drawdecoder.h" #include "digikam_debug.h" #include "coredbbackend.h" #include "coredbtransaction.h" #include "coredbchecker.h" #include "collectionmanager.h" #include "collectionlocation.h" #include "collectionscanner.h" #include "imagequerybuilder.h" #include "collectionscannerobserver.h" #include "digikam_config.h" namespace Digikam { int CoreDbSchemaUpdater::schemaVersion() { - return 9; + return 10; } int CoreDbSchemaUpdater::filterSettingsVersion() { return 7; } int CoreDbSchemaUpdater::uniqueHashVersion() { return 2; } bool CoreDbSchemaUpdater::isUniqueHashUpToDate() { return CoreDbAccess().db()->getUniqueHashVersion() >= uniqueHashVersion(); } // -------------------------------------------------------------------------------------- class CoreDbSchemaUpdater::Private { public: explicit Private() : setError(false), backend(0), albumDB(0), dbAccess(0), observer(0) { } bool setError; QVariant currentVersion; QVariant currentRequiredVersion; CoreDbBackend* backend; CoreDB* albumDB; DbEngineParameters parameters; // legacy CoreDbAccess* dbAccess; QString lastErrorMessage; InitializationObserver* observer; }; CoreDbSchemaUpdater::CoreDbSchemaUpdater(CoreDB* const albumDB, CoreDbBackend* const backend, DbEngineParameters parameters) : d(new Private) { d->backend = backend; d->albumDB = albumDB; d->parameters = parameters; } CoreDbSchemaUpdater::~CoreDbSchemaUpdater() { delete d; } void CoreDbSchemaUpdater::setCoreDbAccess(CoreDbAccess* const dbAccess) { d->dbAccess = dbAccess; } const QString CoreDbSchemaUpdater::getLastErrorMessage() { return d->lastErrorMessage; } bool CoreDbSchemaUpdater::update() { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: running schema update"; bool success = startUpdates(); // cancelled? if (d->observer && !d->observer->continueQuery()) { return false; } // even on failure, try to set current version - it may have incremented setVersionSettings(); if (!success) { return false; } updateFilterSettings(); if (d->observer) { d->observer->finishedSchemaUpdate(InitializationObserver::UpdateSuccess); } return success; } void CoreDbSchemaUpdater::setVersionSettings() { if (d->currentVersion.isValid()) { d->albumDB->setSetting(QLatin1String("DBVersion"), QString::number(d->currentVersion.toInt())); } if (d->currentRequiredVersion.isValid()) { d->albumDB->setSetting(QLatin1String("DBVersionRequired"), QString::number(d->currentRequiredVersion.toInt())); } } static QVariant safeToVariant(const QString& s) { if (s.isEmpty()) { return QVariant(); } else { return s.toInt(); } } void CoreDbSchemaUpdater::readVersionSettings() { d->currentVersion = safeToVariant(d->albumDB->getSetting(QLatin1String("DBVersion"))); d->currentRequiredVersion = safeToVariant(d->albumDB->getSetting(QLatin1String("DBVersionRequired"))); } void CoreDbSchemaUpdater::setObserver(InitializationObserver* const observer) { d->observer = observer; } bool CoreDbSchemaUpdater::startUpdates() { if (!d->parameters.isSQLite()) { // Do we have sufficient privileges QStringList insufficientRights; CoreDbPrivilegesChecker checker(d->parameters); if (!checker.checkPrivileges(insufficientRights)) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: insufficient rights on database."; QString errorMsg = i18n("You have insufficient privileges on the database.\n" "Following privileges are not assigned to you:\n %1\n" "Check your privileges on the database and restart digiKam.", insufficientRights.join(QLatin1String(",\n"))); d->lastErrorMessage = errorMsg; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } } // First step: do we have an empty database? QStringList tables = d->backend->tables(); if (tables.contains(QLatin1String("Albums"), Qt::CaseInsensitive)) { // Find out schema version of db file readVersionSettings(); qCDebug(DIGIKAM_COREDB_LOG) << "Core database: have a structure version " << d->currentVersion.toInt(); // We absolutely require the DBVersion setting if (!d->currentVersion.isValid()) { // Something is damaged. Give up. qCDebug(DIGIKAM_COREDB_LOG) << "Core database: version not available! Giving up schema upgrading."; QString errorMsg = i18n("The database is not valid: " "the \"DBVersion\" setting does not exist. " "The current database schema version cannot be verified. " "Try to start with an empty database. "); d->lastErrorMessage=errorMsg; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } // current version describes the current state of the schema in the db, // schemaVersion is the version required by the program. if (d->currentVersion.toInt() > schemaVersion()) { // trying to open a database with a more advanced than this CoreDbSchemaUpdater supports if (d->currentRequiredVersion.isValid() && d->currentRequiredVersion.toInt() <= schemaVersion()) { // version required may be less than current version return true; } else { QString errorMsg = i18n("The database has been used with a more recent version of digiKam " "and has been updated to a database schema which cannot be used with this version. " "(This means this digiKam version is too old, or the database format is too recent.) " "Please use the more recent version of digiKam that you used before. "); d->lastErrorMessage=errorMsg; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } } else { return makeUpdates(); } } else { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: no database file available"; // Legacy handling? // first test if there are older files that need to be upgraded. // This applies to "digikam.db" for 0.7 and "digikam3.db" for 0.8 and 0.9, // all only SQLite databases. // Version numbers used in this source file are a bit confused for the historic versions. // Version 1 is 0.6 (no db), Version 2 is 0.7 (SQLite 2), // Version 3 is 0.8-0.9, // Version 3 wrote the setting "DBVersion", "1", // Version 4 is 0.10, the digikam3.db file copied to digikam4.db, // no schema changes. // Version 4 writes "4", and from now on version x writes "x". // Version 5 includes the schema changes from 0.9 to 0.10 // Version 6 brought new tables for history and ImageTagProperties, with version 2.0 // Version 7 brought the VideoMetadata table with 3.0 if (d->parameters.isSQLite()) { QFileInfo currentDBFile(d->parameters.databaseNameCore); QFileInfo digikam3DB(currentDBFile.dir(), QLatin1String("digikam3.db")); if (digikam3DB.exists()) { if (!copyV3toV4(digikam3DB.filePath(), currentDBFile.filePath())) { return false; } // d->currentVersion is now 4; return makeUpdates(); } } // No legacy handling: start with a fresh db if (!createDatabase() || !createFilterSettings()) { QString errorMsg = i18n("Failed to create tables in database.\n ") + d->backend->lastError(); d->lastErrorMessage = errorMsg; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } return true; } } bool CoreDbSchemaUpdater::beginWrapSchemaUpdateStep() { if (!d->backend->beginTransaction()) { QFileInfo currentDBFile(d->parameters.databaseNameCore); QString errorMsg = i18n("Failed to open a database transaction on your database file \"%1\". " "This is unusual. Please check that you can access the file and no " "other process has currently locked the file. " "If the problem persists you can get help from the digikam developers mailing list (see www.digikam.org/support). " "As well, please have a look at what digiKam prints on the console. ", QDir::toNativeSeparators(currentDBFile.filePath())); d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); return false; } return true; } bool CoreDbSchemaUpdater::endWrapSchemaUpdateStep(bool stepOperationSuccess, const QString& errorMsg) { if (!stepOperationSuccess) { d->backend->rollbackTransaction(); if (d->observer) { // error or cancelled? if (!d->observer->continueQuery()) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: schema update cancelled by user"; } else if (!d->setError) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } } return false; } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: success updating to version " << d->currentVersion.toInt(); d->backend->commitTransaction(); return true; } bool CoreDbSchemaUpdater::makeUpdates() { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: makeUpdates " << d->currentVersion.toInt() << " to " << schemaVersion(); if (d->currentVersion.toInt() < schemaVersion()) { if (d->currentVersion.toInt() < 5) { if (!beginWrapSchemaUpdateStep()) { return false; } // v4 was always SQLite QFileInfo currentDBFile(d->parameters.databaseNameCore); QString errorMsg = i18n("The schema updating process from version 4 to 6 failed, " "caused by an error that we did not expect. " "You can try to discard your old database and start with an empty one. " "(In this case, please move the database files " "\"%1\" and \"%2\" from the directory \"%3\"). " "More probably you will want to report this error to the digikam developers mailing list (see www.digikam.org/support). " "As well, please have a look at what digiKam prints on the console. ", QLatin1String("digikam3.db"), QLatin1String("digikam4.db"), QDir::toNativeSeparators(currentDBFile.dir().path())); if (!endWrapSchemaUpdateStep(updateV4toV7(), errorMsg)) { return false; } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: success updating v4 to v6"; // Still set these even in >= 1.4 because 0.10 - 1.3 may want to apply the updates if not set setLegacySettingEntries(); } // Incremental updates, starting from version 5 for (int v = d->currentVersion.toInt(); v < schemaVersion(); v++) { int targetVersion = v + 1; if (!beginWrapSchemaUpdateStep()) { return false; } QString errorMsg = i18n("Failed to update the database schema from version %1 to version %2. " "Please read the error messages printed on the console and " "report this error as a bug at bugs.kde.org. ", d->currentVersion.toInt(), targetVersion); if (!endWrapSchemaUpdateStep(updateToVersion(targetVersion), errorMsg)) { return false; } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: success updating to version " << d->currentVersion.toInt(); } // add future updates here } return true; } void CoreDbSchemaUpdater::defaultFilterSettings(QStringList& defaultImageFilter, QStringList& defaultVideoFilter, QStringList& defaultAudioFilter) { //NOTE for updating: //When changing anything here, just increment filterSettingsVersion() so that the changes take effect // https://en.wikipedia.org/wiki/Image_file_formats defaultImageFilter << QLatin1String("jpg") << QLatin1String("jpeg") << QLatin1String("jpe") // JPEG << QLatin1String("jp2") << QLatin1String("j2k") << QLatin1String("jpx") << QLatin1String("jpc") << QLatin1String("pgx") // JPEG-2000 << QLatin1String("tif") << QLatin1String("tiff") // TIFF << QLatin1String("png") // PNG << QLatin1String("gif") << QLatin1String("xpm") << QLatin1String("ppm") << QLatin1String("pnm") << QLatin1String("pgf") << QLatin1String("bmp") << QLatin1String("xcf") << QLatin1String("pcx") << QLatin1String("webp"); defaultImageFilter << DRawDecoder::rawFilesList(); // https://en.wikipedia.org/wiki/Video_file_format defaultVideoFilter << QLatin1String("mpeg") << QLatin1String("mpg") << QLatin1String("mpo") << QLatin1String("mpe") << QLatin1String("mts") << QLatin1String("vob") // MPEG << QLatin1String("avi") << QLatin1String("divx") // RIFF << QLatin1String("wmv") << QLatin1String("wmf") << QLatin1String("asf") // ASF << QLatin1String("mp4") << QLatin1String("3gp") << QLatin1String("mov") << QLatin1String("3g2") << QLatin1String("m4v") << QLatin1String("m2v") // QuickTime << QLatin1String("mkv") << QLatin1String("webm") // Matroska << QLatin1String("mng"); // Animated PNG image // https://en.wikipedia.org/wiki/Audio_file_format defaultAudioFilter << QLatin1String("ogg") << QLatin1String("oga") << QLatin1String("flac") << QLatin1String("wv") << QLatin1String("ape") // Linux audio << QLatin1String("mpc") << QLatin1String("au") // Linux audio << QLatin1String("m4b") << QLatin1String("aax") << QLatin1String("aa") // Book audio << QLatin1String("mp3") << QLatin1String("aac") // MPEG based audio << QLatin1String("m4a") << QLatin1String("m4p") << QLatin1String("caf") << QLatin1String("aiff") // Apple audio << QLatin1String("wma") << QLatin1String("wav"); // Windows audio } void CoreDbSchemaUpdater::defaultIgnoreDirectoryFilterSettings(QStringList& defaultIgnoreDirectoryFilter) { // NOTE: when update this section, // just increment filterSettingsVersion() so that the changes take effect defaultIgnoreDirectoryFilter << QLatin1String("@eaDir"); } bool CoreDbSchemaUpdater::createFilterSettings() { QStringList defaultImageFilter, defaultVideoFilter, defaultAudioFilter, defaultIgnoreDirectoryFilter; defaultFilterSettings(defaultImageFilter, defaultVideoFilter, defaultAudioFilter); defaultIgnoreDirectoryFilterSettings(defaultIgnoreDirectoryFilter); d->albumDB->setFilterSettings(defaultImageFilter, defaultVideoFilter, defaultAudioFilter); d->albumDB->setIgnoreDirectoryFilterSettings(defaultIgnoreDirectoryFilter); d->albumDB->setSetting(QLatin1String("FilterSettingsVersion"), QString::number(filterSettingsVersion())); d->albumDB->setSetting(QLatin1String("DcrawFilterSettingsVersion"), QString::number(DRawDecoder::rawFilesVersion())); return true; } bool CoreDbSchemaUpdater::updateFilterSettings() { QString filterVersion = d->albumDB->getSetting(QLatin1String("FilterSettingsVersion")); QString dcrawFilterVersion = d->albumDB->getSetting(QLatin1String("DcrawFilterSettingsVersion")); if (filterVersion.toInt() < filterSettingsVersion() || dcrawFilterVersion.toInt() < DRawDecoder::rawFilesVersion()) { createFilterSettings(); } return true; } bool CoreDbSchemaUpdater::createDatabase() { if ( createTables() && createIndices() && createTriggers()) { setLegacySettingEntries(); d->currentVersion = schemaVersion(); // if we start with the V2 hash, version 6 is required d->albumDB->setUniqueHashVersion(uniqueHashVersion()); d->currentRequiredVersion = schemaVersion(); /* // Digikam for database version 5 can work with version 6, though not using the new features d->currentRequiredVersion = 5; */ return true; } else { return false; } } bool CoreDbSchemaUpdater::createTables() { return d->backend->execDBAction(d->backend->getDBAction(QLatin1String("CreateDB"))); } bool CoreDbSchemaUpdater::createIndices() { // TODO: see which more indices are needed // create indices return d->backend->execDBAction(d->backend->getDBAction(QLatin1String("CreateIndices"))); } bool CoreDbSchemaUpdater::createTriggers() { return d->backend->execDBAction(d->backend->getDBAction(QLatin1String("CreateTriggers"))); } bool CoreDbSchemaUpdater::updateUniqueHash() { if (isUniqueHashUpToDate()) { return true; } readVersionSettings(); { CoreDbTransaction transaction; CoreDbAccess().db()->setUniqueHashVersion(uniqueHashVersion()); CollectionScanner scanner; scanner.setNeedFileCount(true); scanner.setUpdateHashHint(); if (d->observer) { d->observer->connectCollectionScanner(&scanner); scanner.setObserver(d->observer); } scanner.completeScan(); // earlier digikam does not know about the hash if (d->currentRequiredVersion.toInt() < 6) { d->currentRequiredVersion = 6; setVersionSettings(); } } return true; } bool CoreDbSchemaUpdater::performUpdateToVersion(const QString& actionName, int newVersion, int newRequiredVersion) { if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->moreSchemaUpdateSteps(1); } DbEngineAction updateAction = d->backend->getDBAction(actionName); if (updateAction.name.isNull()) { QString errorMsg = i18n("The database update action cannot be found. Please ensure that " "the dbconfig.xml file of the current version of digiKam is installed " "at the correct place. "); } if (!d->backend->execDBAction(updateAction)) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: schema update to V" << newVersion << "failed!"; // resort to default error message, set above return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Updated schema to version %1.", newVersion)); } d->currentVersion = newVersion; // Digikam for database version 5 can work with version 6, though not using the new features // Note: We do not upgrade the uniqueHash d->currentRequiredVersion = newRequiredVersion; return true; } bool CoreDbSchemaUpdater::updateToVersion(int targetVersion) { if (d->currentVersion != targetVersion-1) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: updateToVersion performs only incremental updates. Called to update from" << d->currentVersion << "to" << targetVersion << ", aborting."; return false; } switch (targetVersion) { case 6: // Digikam for database version 5 can work with version 6, though not using the new features // Note: We do not upgrade the uniqueHash return performUpdateToVersion(QLatin1String("UpdateSchemaFromV5ToV6"), 6, 5); case 7: // Digikam for database version 5 and 6 can work with version 7, though not using the support for video files. return performUpdateToVersion(QLatin1String("UpdateSchemaFromV6ToV7"), 7, 5); // NOTE: If you add a new update step, please check the d->currentVersion at the bottom of updateV4toV7 // If the update already comes with createTables, createTriggers, we don't need the extra update here case 8: // Digikam for database version 7 can work with version 8, now using COLLATE utf8_general_ci for MySQL. return performUpdateToVersion(QLatin1String("UpdateSchemaFromV7ToV9"), 8, 5); case 9: // Digikam for database version 8 can work with version 9, now using COLLATE utf8_general_ci for MySQL. return performUpdateToVersion(QLatin1String("UpdateSchemaFromV7ToV9"), 9, 5); + case 10: + // Digikam for database version 9 can work with version 10, remove ImageHaarMatrix table and add manualOrder column. + return performUpdateToVersion(QLatin1String("UpdateSchemaFromV9ToV10"), 10, 5); default: qCDebug(DIGIKAM_COREDB_LOG) << "Core database: unsupported update to version" << targetVersion; return false; } } bool CoreDbSchemaUpdater::copyV3toV4(const QString& digikam3DBPath, const QString& currentDBPath) { if (d->observer) { d->observer->moreSchemaUpdateSteps(2); } d->backend->close(); // We cannot use KIO here because KIO only works from the main thread QFile oldFile(digikam3DBPath); QFile newFile(currentDBPath); // QFile won't override. Remove the empty db file created when a non-existent file is opened newFile.remove(); if (!oldFile.copy(currentDBPath)) { QString errorMsg = i18n("Failed to copy the old database file (\"%1\") " "to its new location (\"%2\"). " "Error message: \"%3\". " "Please make sure that the file can be copied, " "or delete it.", digikam3DBPath, currentDBPath, oldFile.errorString()); d->lastErrorMessage = errorMsg; d->setError = true; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } if (d->observer) { d->observer->schemaUpdateProgress(i18n("Copied database file")); } if (!d->backend->open(d->parameters)) { QString errorMsg = i18n("The old database file (\"%1\") has been copied " "to the new location (\"%2\") but it cannot be opened. " "Please delete both files and try again, " "starting with an empty database. ", digikam3DBPath, currentDBPath); d->lastErrorMessage = errorMsg; d->setError = true; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } if (d->observer) { d->observer->schemaUpdateProgress(i18n("Opened new database file")); } d->currentVersion = 4; return true; } static QStringList cleanUserFilterString(const QString& filterString) { // splits by either ; or space, removes "*.", trims QStringList filterList; QString wildcard(QLatin1String("*.")); QChar dot(QLatin1Char('.')); QChar sep(QLatin1Char(';')); int i = filterString.indexOf( sep ); if ( i == -1 && filterString.indexOf(QLatin1Char(' ')) != -1 ) { sep = QChar(QLatin1Char(' ')); } QStringList sepList = filterString.split(sep, QString::SkipEmptyParts); foreach(const QString& f, sepList) { if (f.startsWith(wildcard)) { filterList << f.mid(2).trimmed().toLower(); } else { filterList << f.trimmed().toLower(); } } return filterList; } bool CoreDbSchemaUpdater::updateV4toV7() { qCDebug(DIGIKAM_COREDB_LOG) << "Core database : running updateV4toV7"; if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->moreSchemaUpdateSteps(11); } // This update was introduced from digikam version 0.9 to digikam 0.10 // We operator on an SQLite3 database under a transaction (which will be rolled back on error) // --- Make space for new tables --- if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE Albums RENAME TO AlbumsV3;"))) { return false; } if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE Images RENAME TO ImagesV3;"))) { return false; } if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE Searches RENAME TO SearchesV3;"))) { return false; } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: moved tables"; // --- Drop some triggers and indices --- // Don't check for errors here. The "IF EXISTS" clauses seem not supported in SQLite d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_album;")); d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_image;")); d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_tag;")); d->backend->execSql(QString::fromUtf8("DROP TRIGGER insert_tagstree;")); d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_tagstree;")); d->backend->execSql(QString::fromUtf8("DROP TRIGGER move_tagstree;")); d->backend->execSql(QString::fromUtf8("DROP INDEX dir_index;")); d->backend->execSql(QString::fromUtf8("DROP INDEX tag_index;")); if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Prepared table creation")); } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: dropped triggers"; // --- Create new tables --- if (!createTables() || !createIndices()) { return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Created tables")); } // --- Populate AlbumRoots (from config) --- KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(QLatin1String("Album Settings")); QString albumLibraryPath = group.readEntry(QLatin1String("Album Path"), QString()); if (albumLibraryPath.isEmpty()) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: Album library path from config file is empty. Aborting update."; QString errorMsg = i18n("No album library path has been found in the configuration file. " "Giving up the schema updating process. " "Please try with an empty database, or repair your configuration."); d->lastErrorMessage = errorMsg; d->setError = true; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } QUrl albumLibrary(QUrl::fromLocalFile(albumLibraryPath)); CollectionLocation location = CollectionManager::instance()->addLocation(albumLibrary, albumLibrary.fileName()); if (location.isNull()) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: failure to create a collection location. Aborting update."; QString errorMsg = i18n("There was an error associating your albumLibraryPath (\"%1\") " "with a storage volume of your system. " "This problem may indicate that there is a problem with your installation. " "If you are working on Linux, check that HAL is installed and running. " "In any case, you can seek advice from the digikam developers mailing list (see www.digikam.org/support). " "The database updating process will now be aborted because we do not want " "to create a new database based on false assumptions from a broken installation.", albumLibraryPath); d->lastErrorMessage = errorMsg; d->setError = true; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Configured one album root")); } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: inserted album root"; // --- With the album root, populate albums --- if (!d->backend->execSql(QString::fromUtf8( "REPLACE INTO Albums " " (id, albumRoot, relativePath, date, caption, collection, icon) " "SELECT id, ?, url, date, caption, collection, icon " " FROM AlbumsV3;" ), location.id()) ) { return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Imported albums")); } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: populated albums"; // --- Add images --- if (!d->backend->execSql(QString::fromUtf8( "REPLACE INTO Images " " (id, album, name, status, category, modificationDate, fileSize, uniqueHash) " "SELECT id, dirid, name, ?, ?, NULL, NULL, NULL" " FROM ImagesV3;" ), DatabaseItem::Visible, DatabaseItem::UndefinedCategory) ) { return false; } if (!d->dbAccess->backend()->execSql(QString::fromUtf8( "REPLACE INTO ImageInformation (imageId) SELECT id FROM Images;")) ) { return false; } // remove orphan images that would not be removed by CollectionScanner d->backend->execSql(QString::fromUtf8("DELETE FROM Images WHERE album NOT IN (SELECT id FROM Albums);")); if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Imported images information")); } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: populated Images"; // --- Port searches --- if (!d->backend->execSql(QString::fromUtf8( "REPLACE INTO Searches " " (id, type, name, query) " "SELECT id, ?, name, url" " FROM SearchesV3;"), DatabaseSearch::LegacyUrlSearch) ) { return false; } SearchInfo::List sList = d->albumDB->scanSearches(); for (SearchInfo::List::const_iterator it = sList.constBegin(); it != sList.constEnd(); ++it) { QUrl url((*it).query); ImageQueryBuilder builder; QString query = builder.convertFromUrlToXml(url); QString name = (*it).name; if (name == i18n("Last Search")) { name = i18n("Last Search (0.9)"); } if (QUrlQuery(url).queryItemValue(QLatin1String("type")) == QLatin1String("datesearch")) { d->albumDB->updateSearch((*it).id, DatabaseSearch::TimeLineSearch, name, query); } else if (QUrlQuery(url).queryItemValue(QLatin1String("1.key")) == QLatin1String("keyword")) { d->albumDB->updateSearch((*it).id, DatabaseSearch::KeywordSearch, name, query); } else { d->albumDB->updateSearch((*it).id, DatabaseSearch::AdvancedSearch, name, query); } } // --- Create triggers --- if (!createTriggers()) { return false; } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: created triggers"; // --- Populate name filters --- createFilterSettings(); // --- Set user settings from config --- QStringList defaultImageFilter, defaultVideoFilter, defaultAudioFilter; defaultFilterSettings(defaultImageFilter, defaultVideoFilter, defaultAudioFilter); QSet configImageFilter, configVideoFilter, configAudioFilter; configImageFilter = cleanUserFilterString(group.readEntry(QLatin1String("File Filter"), QString())).toSet(); configImageFilter += cleanUserFilterString(group.readEntry(QLatin1String("Raw File Filter"), QString())).toSet(); configVideoFilter = cleanUserFilterString(group.readEntry(QLatin1String("Movie File Filter"), QString())).toSet(); configAudioFilter = cleanUserFilterString(group.readEntry(QLatin1String("Audio File Filter"), QString())).toSet(); // remove those that are included in the default filter configImageFilter.subtract(defaultImageFilter.toSet()); configVideoFilter.subtract(defaultVideoFilter.toSet()); configAudioFilter.subtract(defaultAudioFilter.toSet()); d->albumDB->setUserFilterSettings(configImageFilter.toList(), configVideoFilter.toList(), configAudioFilter.toList()); qCDebug(DIGIKAM_COREDB_LOG) << "Core database: set initial filter settings with user settings" << configImageFilter; if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Initialized and imported file suffix filter")); } // --- do a full scan --- CollectionScanner scanner; if (d->observer) { d->observer->connectCollectionScanner(&scanner); scanner.setObserver(d->observer); } scanner.completeScan(); if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Did the initial full scan")); } // --- Port date, comment and rating (_after_ the scan) --- // Port ImagesV3.date -> ImageInformation.creationDate if (!d->backend->execSql(QString::fromUtf8( "UPDATE ImageInformation SET " " creationDate=(SELECT datetime FROM ImagesV3 WHERE ImagesV3.id=ImageInformation.imageid) " "WHERE imageid IN (SELECT id FROM ImagesV3);") ) ) { return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Imported creation dates")); } // Port ImagesV3.comment to ImageComments // An author of NULL will inhibt the UNIQUE restriction to take effect (but #189080). Work around. d->backend->execSql(QString::fromUtf8( "DELETE FROM ImageComments WHERE " "type=? AND language=? AND author IS NULL " "AND imageid IN ( SELECT id FROM ImagesV3 ); "), (int)DatabaseComment::Comment, QLatin1String("x-default")); if (!d->backend->execSql(QString::fromUtf8( "REPLACE INTO ImageComments " " (imageid, type, language, comment) " "SELECT id, ?, ?, caption FROM ImagesV3;" ), (int)DatabaseComment::Comment, QLatin1String("x-default")) ) { return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Imported comments")); } // Port rating storage in ImageProperties to ImageInformation if (!d->backend->execSql(QString::fromUtf8( "UPDATE ImageInformation SET " " rating=(SELECT value FROM ImageProperties " " WHERE ImageInformation.imageid=ImageProperties.imageid AND ImageProperties.property=?) " "WHERE imageid IN (SELECT imageid FROM ImageProperties WHERE property=?);" ), QString::fromUtf8("Rating"), QString::fromUtf8("Rating")) ) { return false; } d->backend->execSql(QString::fromUtf8("DELETE FROM ImageProperties WHERE property=?;"), QString::fromUtf8("Rating")); d->backend->execSql(QString::fromUtf8("UPDATE ImageInformation SET rating=0 WHERE rating<0;")); if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Imported ratings")); } // --- Drop old tables --- d->backend->execSql(QString::fromUtf8("DROP TABLE ImagesV3;")); d->backend->execSql(QString::fromUtf8("DROP TABLE AlbumsV3;")); d->backend->execSql(QString::fromUtf8("DROP TABLE SearchesV3;")); if (d->observer) { d->observer->schemaUpdateProgress(i18n("Dropped v3 tables")); } d->currentRequiredVersion = 5; d->currentVersion = 7; qCDebug(DIGIKAM_COREDB_LOG) << "Core database: returning true from updating to 5"; return true; } void CoreDbSchemaUpdater::setLegacySettingEntries() { d->albumDB->setSetting(QLatin1String("preAlpha010Update1"), QLatin1String("true")); d->albumDB->setSetting(QLatin1String("preAlpha010Update2"), QLatin1String("true")); d->albumDB->setSetting(QLatin1String("preAlpha010Update3"), QLatin1String("true")); d->albumDB->setSetting(QLatin1String("beta010Update1"), QLatin1String("true")); d->albumDB->setSetting(QLatin1String("beta010Update2"), QLatin1String("true")); } // ---------- Legacy code ------------ void CoreDbSchemaUpdater::preAlpha010Update1() { QString hasUpdate = d->albumDB->getSetting(QLatin1String("preAlpha010Update1")); if (!hasUpdate.isNull()) { return; } if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE Searches RENAME TO SearchesV3;"))) { return; } if ( !d->backend->execSql( QString::fromUtf8( "CREATE TABLE IF NOT EXISTS Searches \n" " (id INTEGER PRIMARY KEY, \n" " type INTEGER, \n" " name TEXT NOT NULL, \n" " query TEXT NOT NULL);" ) )) { return; } if (!d->backend->execSql(QString::fromUtf8( "REPLACE INTO Searches " " (id, type, name, query) " "SELECT id, ?, name, url" " FROM SearchesV3;"), DatabaseSearch::LegacyUrlSearch) ) { return; } SearchInfo::List sList = d->albumDB->scanSearches(); for (SearchInfo::List::const_iterator it = sList.constBegin(); it != sList.constEnd(); ++it) { QUrl url((*it).query); ImageQueryBuilder builder; QString query = builder.convertFromUrlToXml(url); if (QUrlQuery(url).queryItemValue(QLatin1String("type")) == QLatin1String("datesearch")) { d->albumDB->updateSearch((*it).id, DatabaseSearch::TimeLineSearch, (*it).name, query); } else if (QUrlQuery(url).queryItemValue(QLatin1String("1.key")) == QLatin1String("keyword")) { d->albumDB->updateSearch((*it).id, DatabaseSearch::KeywordSearch, (*it).name, query); } else { d->albumDB->updateSearch((*it).id, DatabaseSearch::AdvancedSearch, (*it).name, query); } } d->backend->execSql(QString::fromUtf8("DROP TABLE SearchesV3;")); d->albumDB->setSetting(QLatin1String("preAlpha010Update1"), QLatin1String("true")); } void CoreDbSchemaUpdater::preAlpha010Update2() { QString hasUpdate = d->albumDB->getSetting(QLatin1String("preAlpha010Update2")); if (!hasUpdate.isNull()) { return; } if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE ImagePositions RENAME TO ImagePositionsTemp;"))) { return; } if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE ImageMetadata RENAME TO ImageMetadataTemp;"))) { return; } d->backend->execSql( QString::fromUtf8("CREATE TABLE ImagePositions\n" " (imageid INTEGER PRIMARY KEY,\n" " latitude TEXT,\n" " latitudeNumber REAL,\n" " longitude TEXT,\n" " longitudeNumber REAL,\n" " altitude REAL,\n" " orientation REAL,\n" " tilt REAL,\n" " roll REAL,\n" " accuracy REAL,\n" " description TEXT);") ); d->backend->execSql(QString::fromUtf8("REPLACE INTO ImagePositions " " (imageid, latitude, latitudeNumber, longitude, longitudeNumber, " " altitude, orientation, tilt, roll, accuracy, description) " "SELECT imageid, latitude, latitudeNumber, longitude, longitudeNumber, " " altitude, orientation, tilt, roll, 0, description " " FROM ImagePositionsTemp;")); d->backend->execSql( QString::fromUtf8("CREATE TABLE ImageMetadata\n" " (imageid INTEGER PRIMARY KEY,\n" " make TEXT,\n" " model TEXT,\n" " lens TEXT,\n" " aperture REAL,\n" " focalLength REAL,\n" " focalLength35 REAL,\n" " exposureTime REAL,\n" " exposureProgram INTEGER,\n" " exposureMode INTEGER,\n" " sensitivity INTEGER,\n" " flash INTEGER,\n" " whiteBalance INTEGER,\n" " whiteBalanceColorTemperature INTEGER,\n" " meteringMode INTEGER,\n" " subjectDistance REAL,\n" " subjectDistanceCategory INTEGER);") ); d->backend->execSql( QString::fromUtf8("INSERT INTO ImageMetadata " " (imageid, make, model, lens, aperture, focalLength, focalLength35, " " exposureTime, exposureProgram, exposureMode, sensitivity, flash, whiteBalance, " " whiteBalanceColorTemperature, meteringMode, subjectDistance, subjectDistanceCategory) " "SELECT imageid, make, model, NULL, aperture, focalLength, focalLength35, " " exposureTime, exposureProgram, exposureMode, sensitivity, flash, whiteBalance, " " whiteBalanceColorTemperature, meteringMode, subjectDistance, subjectDistanceCategory " "FROM ImageMetadataTemp;")); d->backend->execSql(QString::fromUtf8("DROP TABLE ImagePositionsTemp;")); d->backend->execSql(QString::fromUtf8("DROP TABLE ImageMetadataTemp;")); d->albumDB->setSetting(QLatin1String("preAlpha010Update2"), QLatin1String("true")); } void CoreDbSchemaUpdater::preAlpha010Update3() { QString hasUpdate = d->albumDB->getSetting(QLatin1String("preAlpha010Update3")); if (!hasUpdate.isNull()) { return; } d->backend->execSql(QString::fromUtf8("DROP TABLE ImageCopyright;")); d->backend->execSql(QString::fromUtf8("CREATE TABLE ImageCopyright\n" " (imageid INTEGER,\n" " property TEXT,\n" " value TEXT,\n" " extraValue TEXT,\n" " UNIQUE(imageid, property, value, extraValue));") ); d->albumDB->setSetting(QLatin1String("preAlpha010Update3"), QLatin1String("true")); } void CoreDbSchemaUpdater::beta010Update1() { QString hasUpdate = d->albumDB->getSetting(QLatin1String("beta010Update1")); if (!hasUpdate.isNull()) { return; } // if Image has been deleted d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_image;")); d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_image DELETE ON Images\n" "BEGIN\n" " DELETE FROM ImageTags\n" " WHERE imageid=OLD.id;\n" " DELETE From ImageHaarMatrix\n " " WHERE imageid=OLD.id;\n" " DELETE From ImageInformation\n " " WHERE imageid=OLD.id;\n" " DELETE From ImageMetadata\n " " WHERE imageid=OLD.id;\n" " DELETE From ImagePositions\n " " WHERE imageid=OLD.id;\n" " DELETE From ImageComments\n " " WHERE imageid=OLD.id;\n" " DELETE From ImageCopyright\n " " WHERE imageid=OLD.id;\n" " DELETE From ImageProperties\n " " WHERE imageid=OLD.id;\n" " UPDATE Albums SET icon=null \n " " WHERE icon=OLD.id;\n" " UPDATE Tags SET icon=null \n " " WHERE icon=OLD.id;\n" "END;")); d->albumDB->setSetting(QLatin1String("beta010Update1"), QLatin1String("true")); } void CoreDbSchemaUpdater::beta010Update2() { QString hasUpdate = d->albumDB->getSetting(QLatin1String("beta010Update2")); if (!hasUpdate.isNull()) { return; } // force rescan and creation of ImageInformation entry for videos and audio d->backend->execSql(QString::fromUtf8("DELETE FROM Images WHERE category=2 OR category=3;")); d->albumDB->setSetting(QLatin1String("beta010Update2"), QLatin1String("true")); } bool CoreDbSchemaUpdater::createTablesV3() { if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE Albums\n" " (id INTEGER PRIMARY KEY,\n" " url TEXT NOT NULL UNIQUE,\n" " date DATE NOT NULL,\n" " caption TEXT,\n" " collection TEXT,\n" " icon INTEGER);") )) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE Tags\n" " (id INTEGER PRIMARY KEY,\n" " pid INTEGER,\n" " name TEXT NOT NULL,\n" " icon INTEGER,\n" " iconkde TEXT,\n" " UNIQUE (name, pid));") )) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE TagsTree\n" " (id INTEGER NOT NULL,\n" " pid INTEGER NOT NULL,\n" " UNIQUE (id, pid));") )) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE Images\n" " (id INTEGER PRIMARY KEY,\n" " name TEXT NOT NULL,\n" " dirid INTEGER NOT NULL,\n" " caption TEXT,\n" " datetime DATETIME,\n" " UNIQUE (name, dirid));") )) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE ImageTags\n" " (imageid INTEGER NOT NULL,\n" " tagid INTEGER NOT NULL,\n" " UNIQUE (imageid, tagid));") )) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE ImageProperties\n" " (imageid INTEGER NOT NULL,\n" " property TEXT NOT NULL,\n" " value TEXT NOT NULL,\n" " UNIQUE (imageid, property));") )) { return false; } if ( !d->backend->execSql( QString::fromUtf8("CREATE TABLE Searches \n" " (id INTEGER PRIMARY KEY, \n" " name TEXT NOT NULL UNIQUE, \n" " url TEXT NOT NULL);") ) ) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE Settings \n" "(keyword TEXT NOT NULL UNIQUE,\n" " value TEXT);") )) { return false; } // TODO: see which more indices are needed // create indices d->backend->execSql(QString::fromUtf8("CREATE INDEX dir_index ON Images (dirid);")); d->backend->execSql(QString::fromUtf8("CREATE INDEX tag_index ON ImageTags (tagid);")); // create triggers // trigger: delete from Images/ImageTags/ImageProperties // if Album has been deleted d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_album DELETE ON Albums\n" "BEGIN\n" " DELETE FROM ImageTags\n" " WHERE imageid IN (SELECT id FROM Images WHERE dirid=OLD.id);\n" " DELETE From ImageProperties\n" " WHERE imageid IN (SELECT id FROM Images WHERE dirid=OLD.id);\n" " DELETE FROM Images\n" " WHERE dirid = OLD.id;\n" "END;")); // trigger: delete from ImageTags/ImageProperties // if Image has been deleted d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_image DELETE ON Images\n" "BEGIN\n" " DELETE FROM ImageTags\n" " WHERE imageid=OLD.id;\n" " DELETE From ImageProperties\n " " WHERE imageid=OLD.id;\n" " UPDATE Albums SET icon=null \n " " WHERE icon=OLD.id;\n" " UPDATE Tags SET icon=null \n " " WHERE icon=OLD.id;\n" "END;")); // trigger: delete from ImageTags if Tag has been deleted d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_tag DELETE ON Tags\n" "BEGIN\n" " DELETE FROM ImageTags WHERE tagid=OLD.id;\n" "END;")); // trigger: insert into TagsTree if Tag has been added d->backend->execSql(QString::fromUtf8("CREATE TRIGGER insert_tagstree AFTER INSERT ON Tags\n" "BEGIN\n" " INSERT INTO TagsTree\n" " SELECT NEW.id, NEW.pid\n" " UNION\n" " SELECT NEW.id, pid FROM TagsTree WHERE id=NEW.pid;\n" "END;")); // trigger: delete from TagsTree if Tag has been deleted d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_tagstree DELETE ON Tags\n" "BEGIN\n" " DELETE FROM Tags\n" " WHERE id IN (SELECT id FROM TagsTree WHERE pid=OLD.id);\n" " DELETE FROM TagsTree\n" " WHERE id IN (SELECT id FROM TagsTree WHERE pid=OLD.id);\n" " DELETE FROM TagsTree\n" " WHERE id=OLD.id;\n" "END;")); // trigger: delete from TagsTree if Tag has been deleted d->backend->execSql(QString::fromUtf8("CREATE TRIGGER move_tagstree UPDATE OF pid ON Tags\n" "BEGIN\n" " DELETE FROM TagsTree\n" " WHERE\n" " ((id = OLD.id)\n" " OR\n" " id IN (SELECT id FROM TagsTree WHERE pid=OLD.id))\n" " AND\n" " pid IN (SELECT pid FROM TagsTree WHERE id=OLD.id);\n" " INSERT INTO TagsTree\n" " SELECT NEW.id, NEW.pid\n" " UNION\n" " SELECT NEW.id, pid FROM TagsTree WHERE id=NEW.pid\n" " UNION\n" " SELECT id, NEW.pid FROM TagsTree WHERE pid=NEW.id\n" " UNION\n" " SELECT A.id, B.pid FROM TagsTree A, TagsTree B\n" " WHERE\n" " A.pid = NEW.id AND B.id = NEW.pid;\n" "END;")); return true; } } // namespace Digikam diff --git a/core/libs/database/tags/facetags.cpp b/core/libs/database/tags/facetags.cpp index dc1f965ce2..2b2ed01c5a 100644 --- a/core/libs/database/tags/facetags.cpp +++ b/core/libs/database/tags/facetags.cpp @@ -1,449 +1,450 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2011-08-08 * Description : Accessing face tags * * Copyright (C) 2010-2011 by Aditya Bhatt * Copyright (C) 2010-2011 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 "facetags.h" // KDE includes #include // Local includes #include "digikam_debug.h" #include "coredbconstants.h" #include "tagscache.h" #include "tagregion.h" #include "tagproperties.h" namespace Digikam { // --- FaceIfacePriv ---------------------------------------------------------------------------------------- class FaceTagsHelper { public: static QString tagPath(const QString& name, int parentId); static void makeFaceTag(int tagId, const QString& fullName); static int findFirstTagWithProperty(const QString& property, const QString& value = QString()); static int tagForName(const QString& name, int tagId, int parentId, const QString& givenFullName, bool convert, bool create); }; // --- Private methods --- int FaceTagsHelper::findFirstTagWithProperty(const QString& property, const QString& value) { QList candidates = TagsCache::instance()->tagsWithProperty(property, value); if (!candidates.isEmpty()) { return candidates.first(); } return 0; } QString FaceTagsHelper::tagPath(const QString& name, int parentId) { QString faceParentTagName = TagsCache::instance()->tagName(parentId); if ((faceParentTagName).contains(QRegExp(QLatin1String("(_Digikam_root_tag_/|/_Digikam_root_tag_|_Digikam_root_tag_)")))) { return QLatin1Char('/') + name; } else { return faceParentTagName + QLatin1Char('/') + name; } } void FaceTagsHelper::makeFaceTag(int tagId, const QString& fullName) { QString faceEngineName = fullName; /* * // find a unique FacesEngineId * for (int i=0; d->findFirstTagWithProperty(TagPropertyName::FacesEngineId(), FacesEngineId); ++i) * FacesEngineId = fullName + QString::fromUtf8(" (%1)").arg(i); */ TagProperties props(tagId); props.setProperty(TagPropertyName::person(), fullName); props.setProperty(TagPropertyName::faceEngineName(), faceEngineName); } int FaceTagsHelper::tagForName(const QString& name, int tagId, int parentId, const QString& givenFullName, bool convert, bool create) { if (name.isEmpty() && givenFullName.isEmpty() && !tagId) { return FaceTags::unknownPersonTagId(); } QString fullName = givenFullName.isNull() ? name : givenFullName; if (tagId) { if (FaceTags::isPerson(tagId)) { //qCDebug(DIGIKAM_DATABASE_LOG) << "Proposed tag is already a person"; return tagId; } else if (convert) { if (fullName.isNull()) { fullName = TagsCache::instance()->tagName(tagId); } qCDebug(DIGIKAM_DATABASE_LOG) << "Converting proposed tag to person, full name" << fullName; makeFaceTag(tagId, fullName); return tagId; } return 0; } // First attempt: Find by full name in "person" attribute QList candidates = TagsCache::instance()->tagsWithProperty(TagPropertyName::person(), fullName); foreach(int id, candidates) { qCDebug(DIGIKAM_DATABASE_LOG) << "Candidate with set full name:" << id << fullName; if (parentId == -1) { return id; } else if (TagsCache::instance()->parentTag(id) == parentId) { return id; } } // Second attempt: Find by tag name if (parentId == -1) { candidates = TagsCache::instance()->tagsForName(name); } else { tagId = TagsCache::instance()->tagForName(name, parentId); candidates.clear(); if (tagId) { candidates << tagId; } } foreach(int id, candidates) { // Is this tag already a person tag? if (FaceTags::isPerson(id)) { qCDebug(DIGIKAM_DATABASE_LOG) << "Found tag with name" << name << "is already a person." << id; return id; } else if (convert) { qCDebug(DIGIKAM_DATABASE_LOG) << "Converting tag with name" << name << "to a person." << id; makeFaceTag(id, fullName); return id; } } // Third: If desired, create a new tag if (create) { qCDebug(DIGIKAM_DATABASE_LOG) << "Creating new tag for name" << name << "fullName" << fullName; if (parentId == -1) { parentId = FaceTags::personParentTag(); } tagId = TagsCache::instance()->getOrCreateTag(tagPath(name, parentId)); makeFaceTag(tagId, fullName); return tagId; } return 0; } // --- public methods --- QList FaceTags::allPersonNames() { return TagsCache::instance()->tagNames(allPersonTags()); } QList FaceTags::allPersonPaths() { return TagsCache::instance()->tagPaths(allPersonTags()); } int FaceTags::tagForPerson(const QString& name, int parentId, const QString& fullName) { return FaceTagsHelper::tagForName(name, 0, parentId, fullName, false, false); } int FaceTags::getOrCreateTagForPerson(const QString& name, int parentId, const QString& fullName) { return FaceTagsHelper::tagForName(name, 0, parentId, fullName, true, true); } void FaceTags::ensureIsPerson(int tagId, const QString& fullName) { FaceTagsHelper::tagForName(QString(), tagId, 0, fullName, true, false); } bool FaceTags::isPerson(int tagId) { return TagsCache::instance()->hasProperty(tagId, TagPropertyName::person()); } bool FaceTags::isTheUnknownPerson(int tagId) { return TagsCache::instance()->hasProperty(tagId, TagPropertyName::unknownPerson()); } bool FaceTags::isTheUnconfirmedPerson(int tagId) { return TagsCache::instance()->hasProperty(tagId, TagPropertyName::unconfirmedPerson()); } QList FaceTags::allPersonTags() { return TagsCache::instance()->tagsWithProperty(TagPropertyName::person()); } int FaceTags::scannedForFacesTagId() { return TagsCache::instance()->getOrCreateInternalTag(InternalTagName::scannedForFaces()); // no i18n } QMap FaceTags::identityAttributes(int tagId) { QMap attributes; QString uuid = TagsCache::instance()->propertyValue(tagId, TagPropertyName::faceEngineUuid()); if (!uuid.isEmpty()) { attributes[QLatin1String("uuid")] = uuid; } QString fullName = TagsCache::instance()->propertyValue(tagId, TagPropertyName::person()); if (!fullName.isEmpty()) { attributes[QLatin1String("fullName")] = fullName; } QString faceEngineName = TagsCache::instance()->propertyValue(tagId, TagPropertyName::person()); - QString tagName = TagsCache::instance()->tagName(tagId); + QString tagName = TagsCache::instance()->tagName(tagId); if (tagName != faceEngineName) { attributes.insertMulti(QLatin1String("name"), faceEngineName); attributes.insertMulti(QLatin1String("name"), tagName); } else { attributes[QLatin1String("name")] = tagName; } return attributes; } void FaceTags::applyTagIdentityMapping(int tagId, const QMap& attributes) { TagProperties props(tagId); if (attributes.contains(QLatin1String("fullName"))) { props.setProperty(TagPropertyName::person(), attributes.value(QLatin1String("fullName"))); } // we do not change the digikam tag name at this point, but we have this extra tag property if (attributes.contains(QLatin1String("name"))) { props.setProperty(TagPropertyName::faceEngineName(), attributes.value(QLatin1String("name"))); } props.setProperty(TagPropertyName::faceEngineUuid(), attributes.value(QLatin1String("uuid"))); } int FaceTags::getOrCreateTagForIdentity(const QMap& attributes) { // Attributes from FacesEngine's Identity object. // The text constants are defines in FacesEngine's API docs if (attributes.isEmpty()) { return FaceTags::unknownPersonTagId(); } int tagId; // First, look for UUID if (!attributes.value(QLatin1String("uuid")).isEmpty()) { if ( (tagId = FaceTagsHelper::findFirstTagWithProperty(TagPropertyName::faceEngineUuid(), attributes.value(QLatin1String("uuid")))) ) { return tagId; } } // Second, look for full name if (!attributes.value(QLatin1String("fullName")).isEmpty()) { if ( (tagId = FaceTagsHelper::findFirstTagWithProperty(TagPropertyName::person(), attributes.value(QLatin1String("fullName")))) ) { return tagId; } } // Third, look for either name or full name // TODO: better support for "fullName" QString name = attributes.value(QLatin1String("name")); if (name.isEmpty()) { name = attributes.value(QLatin1String("fullName")); } if (name.isEmpty()) { return FaceTags::unknownPersonTagId(); } if ( (tagId = FaceTagsHelper::findFirstTagWithProperty(TagPropertyName::faceEngineName(), name)) ) { return tagId; } if ( (tagId = FaceTagsHelper::findFirstTagWithProperty(TagPropertyName::person(), name)) ) { return tagId; } // identity is in FacesEngine's database, but not in ours, so create. tagId = FaceTagsHelper::tagForName(name, 0, -1, attributes.value(QLatin1String("fullName")), true, true); applyTagIdentityMapping(tagId, attributes); return tagId; } QString FaceTags::faceNameForTag(int tagId) { if (!TagsCache::instance()->hasTag(tagId)) { return QString(); } QString id = TagsCache::instance()->propertyValue(tagId, TagPropertyName::person()); if (id.isNull()) { id = TagsCache::instance()->tagName(tagId); } return id; } int FaceTags::personParentTag() { // check default QString i18nName = i18nc("People on your photos", "People"); int tagId = TagsCache::instance()->tagForPath(i18nName); if (tagId) { return tagId; } // employ a heuristic QList personTags = allPersonTags(); if (!personTags.isEmpty()) { // we find the most toplevel parent tag of a person tag - QMultiMap tiers; + QMultiMap tiers; foreach(int tagId, personTags) { tiers.insert(TagsCache::instance()->parentTags(tagId).size(), tagId); } QList mosttoplevelTags = tiers.values(tiers.begin().key()); // as a pretty weak criterion, take the largest id which usually corresponds to the latest tag creation. std::sort(mosttoplevelTags.begin(), mosttoplevelTags.end()); + return TagsCache::instance()->parentTag(mosttoplevelTags.last()); } // create default return TagsCache::instance()->getOrCreateTag(i18nName); } int FaceTags::unknownPersonTagId() { QList ids = TagsCache::instance()->tagsWithPropertyCached(TagPropertyName::unknownPerson()); if (!ids.isEmpty()) { return ids.first(); } int unknownPersonTagId = TagsCache::instance()->getOrCreateTag( FaceTagsHelper::tagPath( i18nc("The list of detected faces from the collections but not recognized", "Unknown"), personParentTag())); TagProperties props(unknownPersonTagId); props.setProperty(TagPropertyName::person(), QString()); // no name associated props.setProperty(TagPropertyName::unknownPerson(), QString()); // special property return unknownPersonTagId; } int FaceTags::unconfirmedPersonTagId() { QList ids = TagsCache::instance()->tagsWithPropertyCached(TagPropertyName::unconfirmedPerson()); if (!ids.isEmpty()) { return ids.first(); } int unknownPersonTagId = TagsCache::instance()->getOrCreateTag( FaceTagsHelper::tagPath( i18nc("The list of recognized faces from the collections but not confirmed", "Unconfirmed"), personParentTag())); TagProperties props(unknownPersonTagId); - props.setProperty(TagPropertyName::person(), QString()); // no name associated + props.setProperty(TagPropertyName::person(), QString()); // no name associated props.setProperty(TagPropertyName::unconfirmedPerson(), QString()); // special property return unknownPersonTagId; } } // Namespace Digikam diff --git a/core/libs/dtrash/dtrash.cpp b/core/libs/dtrash/dtrash.cpp index 6b66a31655..cf2e3f5a29 100644 --- a/core/libs/dtrash/dtrash.cpp +++ b/core/libs/dtrash/dtrash.cpp @@ -1,240 +1,240 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2015-07-27 * Description : Special digiKam trash implementation * * Copyright (C) 2015 by Mohamed_Anwer * * 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 "dtrash.h" // Qt includes #include #include #include #include #include #include #include #include // Local includes #include "digikam_debug.h" #include "collectionmanager.h" #include "albummanager.h" namespace Digikam { const QString DTrash::TRASH_FOLDER = QLatin1String(".dtrash"); const QString DTrash::FILES_FOLDER = QLatin1String("files"); const QString DTrash::INFO_FOLDER = QLatin1String("info"); const QString DTrash::INFO_FILE_EXTENSION = QLatin1String(".dtrashinfo"); const QString DTrash::PATH_JSON_KEY = QLatin1String("path"); const QString DTrash::DELETIONTIMESTAMP_JSON_KEY = QLatin1String("deletiontimestamp"); const QString DTrash::IMAGEID_JSON_KEY = QLatin1String("imageid"); // ---------------------------------------------- DTrash::DTrash() { } bool DTrash::deleteImage(const QString& imageToDelete) { QString collection = CollectionManager::instance()->albumRootPath(imageToDelete); qCDebug(DIGIKAM_IOJOB_LOG) << "DTrash: Image album root path:" << collection; if (!prepareCollectionTrash(collection)) { return false; } QFileInfo imageFileInfo(imageToDelete); QFile imageFile(imageToDelete); QString fileName = imageFileInfo.fileName(); // Get the album path, i.e. collection + album. For this, // get the n leftmost characters where n is the complete path without the size of the filename QString completePath = imageFileInfo.path(); qlonglong imageId = -1; // Get the album and with this the image id of the image to trash. PAlbum* pAlbum = AlbumManager::instance()->findPAlbum(QUrl::fromLocalFile(completePath)); if (pAlbum) { imageId = AlbumManager::instance()->getItemFromAlbum(pAlbum, fileName); } QString baseNameForMovingIntoTrash = createJsonRecordForFile(collection, imageToDelete, imageId); QString destinationInTrash = collection + QLatin1Char('/') + TRASH_FOLDER + QLatin1Char('/') + FILES_FOLDER + QLatin1Char('/') + - baseNameForMovingIntoTrash + QLatin1String(".") + + baseNameForMovingIntoTrash + QLatin1Char('.') + imageFileInfo.completeSuffix(); if (!imageFile.rename(destinationInTrash)) { return false; } return true; } bool DTrash::deleteDirRecursivley(const QString& dirToDelete) { QDir srcDir(dirToDelete); foreach (const QFileInfo& fileInfo, srcDir.entryInfoList(QDir::Files)) { if (!deleteImage(fileInfo.filePath())) { return false; } } foreach (const QFileInfo& fileInfo, srcDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) { deleteDirRecursivley(fileInfo.filePath()); } return srcDir.removeRecursively(); } void DTrash::extractJsonForItem(const QString& collPath, const QString& baseName, DTrashItemInfo& itemInfo) { QString jsonFilePath = collPath + QLatin1Char('/') + TRASH_FOLDER + QLatin1Char('/') + INFO_FOLDER + QLatin1Char('/') + baseName + INFO_FILE_EXTENSION; QFile jsonFile(jsonFilePath); if (!jsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) return; QJsonDocument doc = QJsonDocument::fromJson(jsonFile.readAll()); jsonFile.close(); QJsonObject fileInfoObj = doc.object(); itemInfo.jsonFilePath = jsonFilePath; itemInfo.collectionPath = fileInfoObj.value(PATH_JSON_KEY).toString(); itemInfo.collectionRelativePath = fileInfoObj.value(PATH_JSON_KEY).toString() .replace(collPath, QLatin1String("")); itemInfo.deletionTimestamp = QDateTime::fromString( fileInfoObj.value(DELETIONTIMESTAMP_JSON_KEY).toString()); QJsonValue imageIdValue = fileInfoObj.value(IMAGEID_JSON_KEY); if (!imageIdValue.isUndefined()) { itemInfo.imageId = imageIdValue.toString().toLongLong(); } else { itemInfo.imageId = -1; } } bool DTrash::prepareCollectionTrash(const QString& collectionPath) { QString trashFolder = collectionPath + QLatin1Char('/') + TRASH_FOLDER; QDir trashDir(trashFolder); if (!trashDir.exists()) { bool isCreated = true; isCreated &= trashDir.mkpath(trashFolder); isCreated &= trashDir.mkpath(trashFolder + QLatin1Char('/') + FILES_FOLDER); isCreated &= trashDir.mkpath(trashFolder + QLatin1Char('/') + INFO_FOLDER); if (!isCreated) { qCDebug(DIGIKAM_IOJOB_LOG) << "DTrash: could not create trash folder for collection"; return false; } } qCDebug(DIGIKAM_IOJOB_LOG) << "Trash folder for collection: " << trashFolder; return true; } QString DTrash::createJsonRecordForFile(const QString& collectionPath, const QString& imagePath, qlonglong imageId) { QJsonObject jsonObjForImg; QJsonValue pathJsonVal(imagePath); QJsonValue timestampJsonVal(QDateTime::currentDateTime().toString()); QJsonValue imageIdJsonVal(QString::number(imageId)); jsonObjForImg.insert(PATH_JSON_KEY, pathJsonVal); jsonObjForImg.insert(DELETIONTIMESTAMP_JSON_KEY, timestampJsonVal); jsonObjForImg.insert(IMAGEID_JSON_KEY, imageIdJsonVal); QJsonDocument jsonDocForImg(jsonObjForImg); QFileInfo imgFileInfo(imagePath); QString jsonFileName = getAvialableJsonFilePathInTrash(collectionPath, imgFileInfo.baseName()); QFile jsonFileForImg(jsonFileName); QFileInfo jsonFileInfo(jsonFileName); if (!jsonFileForImg.open(QFile::WriteOnly)) return jsonFileInfo.baseName(); jsonFileForImg.write(jsonDocForImg.toJson()); jsonFileForImg.close(); return jsonFileInfo.baseName(); } QString DTrash::getAvialableJsonFilePathInTrash(const QString& collectionPath, const QString& baseName, int version) { QString pathToCreateJsonFile = collectionPath + QLatin1Char('/') + TRASH_FOLDER + QLatin1Char('/') + INFO_FOLDER + QLatin1Char('/') + baseName + QLatin1Char('-') + QUuid::createUuid().toString().mid(1, 8) + (version ? QString::number(version) : QLatin1String("")) + INFO_FILE_EXTENSION; QFileInfo jsonFileInfo(pathToCreateJsonFile); if (jsonFileInfo.exists()) { return getAvialableJsonFilePathInTrash(collectionPath, baseName, ++version); } else { return pathToCreateJsonFile; } } } // namespace Digikam diff --git a/core/libs/tags/tagmodificationhelper.cpp b/core/libs/tags/tagmodificationhelper.cpp index 222e323c5c..a5b2881370 100644 --- a/core/libs/tags/tagmodificationhelper.cpp +++ b/core/libs/tags/tagmodificationhelper.cpp @@ -1,710 +1,710 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2000-12-05 * Description : helper class used to modify tag albums in views * * Copyright (C) 2009-2010 by Johannes Wienke * Copyright (C) 2010-2018 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 "tagmodificationhelper.h" // Qt includes #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "album.h" #include "coredb.h" #include "coredbtransaction.h" #include "imageinfo.h" #include "imagetagpair.h" #include "metadatahub.h" #include "scancontroller.h" #include "statusprogressbar.h" #include "tagsactionmngr.h" #include "tagproperties.h" #include "tageditdlg.h" #include "facetags.h" #include "facedbaccess.h" #include "facedb.h" namespace Digikam { class TagModificationHelper::Private { public: explicit Private() { parentTag = 0; dialogParent = 0; } AlbumPointer parentTag; QWidget* dialogParent; }; TagModificationHelper::TagModificationHelper(QObject* const parent, QWidget* const dialogParent) : QObject(parent), d(new Private) { d->dialogParent = dialogParent; } TagModificationHelper::~TagModificationHelper() { delete d; } void TagModificationHelper::bindTag(QAction* action, TAlbum* album) const { action->setData(QVariant::fromValue(AlbumPointer(album))); } TAlbum* TagModificationHelper::boundTag(QObject* sender) const { QAction* action = 0; if ( (action = qobject_cast(sender)) ) { return action->data().value >(); } return 0; } void TagModificationHelper::bindMultipleTags(QAction* action, QList tags) { action->setData(QVariant::fromValue(tags)); } QList TagModificationHelper::boundMultipleTags(QObject* sender) { QAction* action = 0; if ((action = qobject_cast(sender))) { return (action->data().value >()); } return QList(); } TAlbum* TagModificationHelper::slotTagNew(TAlbum* parent, const QString& title, const QString& iconName) { // ensure that there is a parent AlbumPointer p(parent); if (!p) { p = AlbumManager::instance()->findTAlbum(0); if (!p) { qCDebug(DIGIKAM_GENERAL_LOG) << "Could not find root tag album"; return 0; } } QString editTitle = title; QString editIconName = iconName; QKeySequence ks; if (title.isEmpty()) { bool doCreate = TagEditDlg::tagCreate(d->dialogParent, p, editTitle, editIconName, ks); if (!doCreate || !p) { return 0; } } QMap errMap; AlbumList tList = TagEditDlg::createTAlbum(p, editTitle, editIconName, ks, errMap); TagEditDlg::showtagsListCreationError(d->dialogParent, errMap); if (errMap.isEmpty() && !tList.isEmpty()) { TAlbum* const tag = static_cast(tList.last()); emit tagCreated(tag); return tag; } else { return 0; } } TAlbum* TagModificationHelper::slotTagNew() { return slotTagNew(boundTag(sender())); } void TagModificationHelper::slotTagEdit(TAlbum* t) { if (!t) { return; } AlbumPointer tag(t); QString title, icon; QKeySequence ks; bool doEdit = TagEditDlg::tagEdit(d->dialogParent, tag, title, icon, ks); if (!doEdit || !tag) { return; } if (tag && tag->title() != title) { QString errMsg; if (AlbumManager::instance()->renameTAlbum(tag, title, errMsg)) { // TODO: make an option to edit the full name of a face tag if (FaceTags::isPerson(tag->id())) { TagProperties props(tag->id()); props.setProperty(TagPropertyName::person(), title); props.setProperty(TagPropertyName::faceEngineName(), title); } } else { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg); } } if (tag && tag->icon() != icon) { QString errMsg; if (!AlbumManager::instance()->updateTAlbumIcon(tag, icon, 0, errMsg)) { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg); } } if (tag && tag->property(TagPropertyName::tagKeyboardShortcut()) != ks.toString()) { TagsActionMngr::defaultManager()->updateTagShortcut(tag->id(), ks); } emit tagEdited(tag); } void TagModificationHelper::slotTagEdit() { slotTagEdit(boundTag(sender())); } void TagModificationHelper::slotTagDelete(TAlbum* t) { if (!t || t->isRoot()) { return; } AlbumPointer tag(t); // find number of subtags int children = 0; AlbumIterator iter(tag); while (iter.current()) { ++children; ++iter; } // ask for deletion of children if (children) { int result = QMessageBox::warning(d->dialogParent, qApp->applicationName(), i18np("Tag '%2' has one subtag. " "Deleting this will also delete " "the subtag.\n" "Do you want to continue?", "Tag '%2' has %1 subtags. " "Deleting this will also delete " "the subtags.\n" "Do you want to continue?", children, tag->title()), QMessageBox::Yes | QMessageBox::Cancel); if (result != QMessageBox::Yes || !tag) { return; } } QString message; QList assignedItems = CoreDbAccess().db()->getItemIDsInTag(tag->id()); if (!assignedItems.isEmpty()) { message = i18np("Tag '%2' is assigned to one item. " "Do you want to continue?", "Tag '%2' is assigned to %1 items. " "Do you want to continue?", assignedItems.count(), tag->title()); } else { message = i18n("Delete '%1' tag?", tag->title()); } int result = QMessageBox::warning(qApp->activeWindow(), i18n("Delete Tag"), message, QMessageBox::Yes | QMessageBox::Cancel); if (result == QMessageBox::Yes && tag) { emit aboutToDeleteTag(tag); QString errMsg; if (!AlbumManager::instance()->deleteTAlbum(tag, errMsg)) { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg); } } } void TagModificationHelper::slotTagDelete() { slotTagDelete(boundTag(sender())); } -void TagModificationHelper::slotMultipleTagDel(QList& tags) +void TagModificationHelper::slotMultipleTagDel(QList& tags) { QString tagWithChildrens; QString tagWithoutImages; QString tagWithImages; QMultiMap sortedTags; foreach(TAlbum* const t, tags) { if (!t || t->isRoot()) { continue; } AlbumPointer tag(t); // find number of subtags int children = 0; AlbumIterator iter(tag); while (iter.current()) { ++children; ++iter; } if (children) - tagWithChildrens.append(tag->title() + QLatin1String(" ")); + tagWithChildrens.append(tag->title() + QLatin1Char(' ')); QList assignedItems = CoreDbAccess().db()->getItemIDsInTag(tag->id()); if (!assignedItems.isEmpty()) { - tagWithImages.append(tag->title() + QLatin1String(" ")); + tagWithImages.append(tag->title() + QLatin1Char(' ')); } else { - tagWithoutImages.append(tag->title() + QLatin1String(" ")); + tagWithoutImages.append(tag->title() + QLatin1Char(' ')); } /** * Tags must be deleted from children to parents, if we don't want * to step on invalid index. Use QMultiMap to order them by distance * to root tag */ Album* parent = t; int depth = 0; while (!parent->isRoot()) { parent = parent->parent(); depth++; } sortedTags.insert(depth, tag); } // ask for deletion of children if (!tagWithChildrens.isEmpty()) { int result = QMessageBox::warning(qApp->activeWindow(), qApp->applicationName(), i18n("Tags '%1' have one or more subtags. " "Deleting them will also delete " "the subtags.\n" "Do you want to continue?", tagWithChildrens), QMessageBox::Yes | QMessageBox::Cancel); if (result != QMessageBox::Yes) { return; } } QString message; if (!tagWithImages.isEmpty()) { message = i18n("Tags '%1' are assigned to one or more items. " "Do you want to continue?", tagWithImages); } else { message = i18n("Delete '%1' tag(s)?", tagWithoutImages); } int result = QMessageBox::warning(qApp->activeWindow(), i18n("Delete Tag"), message, QMessageBox::Yes | QMessageBox::Cancel); if (result == QMessageBox::Yes) { QMultiMap::iterator it; /** * QMultimap doesn't provide reverse iterator, -1 is required * because end() points after the last element */ for (it = sortedTags.end()-1 ; it != sortedTags.begin()-1 ; --it) { emit aboutToDeleteTag(it.value()); QString errMsg; if (!AlbumManager::instance()->deleteTAlbum(it.value(), errMsg)) { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg); } } } } void TagModificationHelper::slotMultipleTagDel() { QList lst = boundMultipleTags(sender()); qCDebug(DIGIKAM_GENERAL_LOG) << lst.size(); slotMultipleTagDel(lst); } void TagModificationHelper::slotFaceTagDelete(TAlbum* t) { QList tag; tag.append(t); slotMultipleFaceTagDel(tag); } void TagModificationHelper::slotFaceTagDelete() { slotFaceTagDelete(boundTag(sender())); } void TagModificationHelper::slotMultipleFaceTagDel(QList& tags) { QString tagsWithChildren; QString tagsWithImages; // We use a set here since else one tag could occur more than once // which could lead to undefined behaviour. QSet allPersonTagsToDelete; int tagsWithChildrenCount = 0; QSet allAssignedItems; int tagsWithImagesCount = 0; foreach(TAlbum* const selectedTag, tags) { if (!selectedTag || selectedTag->isRoot()) { continue; } // find tags and subtags with person property QSet personTagsToDelete = getFaceTags(selectedTag).toSet(); // If there is more than one person tag in the list, // the tag to remove has at least one sub tag that is a face tag. // Thus, we have to warn. // // If there is only one face tag, it is either the original tag, // or it is the only sub tag of the original tag. // Behave, like the face tag itself was selected. if (personTagsToDelete.size() > 1) { if (tagsWithChildrenCount > 0) { tagsWithChildren.append(QLatin1String(",")); } tagsWithChildren.append(selectedTag->title()); ++tagsWithChildrenCount; } // Get the assigned faces for all person tags to delete foreach(TAlbum* const tAlbum, personTagsToDelete) { // If the global set does not yet contain the tag if (!allPersonTagsToDelete.contains(tAlbum)) { QSet assignedItems = CoreDbAccess().db()->getImagesWithImageTagProperty( tAlbum->id(), Digikam::ImageTagPropertyName::tagRegion()).toSet(); assignedItems.unite(CoreDbAccess().db()->getImagesWithImageTagProperty( tAlbum->id(), Digikam::ImageTagPropertyName::autodetectedFace()).toSet()); if (!assignedItems.isEmpty()) { // Add the items to the global set for potential untagging allAssignedItems.unite(assignedItems); if (tagsWithImagesCount > 0) { tagsWithImages.append(QLatin1String(",")); } tagsWithImages.append(tAlbum->title()); ++tagsWithImagesCount; } } } // Add the found tags to the global set. allPersonTagsToDelete.unite(personTagsToDelete); } // ask for deletion of children if (tagsWithChildrenCount) { QString message = i18np("Face tag '%2' has at least one face tag child. " "Deleting it will also delete the children.\n" "Do you want to continue?", "Face tags '%2' have at least one face tag child. " "Deleting it will also delete the children.\n" "Do you want to continue?", tagsWithChildrenCount, tagsWithChildren); bool removeChildren = QMessageBox::Yes == (QMessageBox::warning(qApp->activeWindow(), qApp->applicationName(), message, QMessageBox::Yes | QMessageBox::Cancel)); if (!removeChildren) { return; } } QString message; if (!allAssignedItems.isEmpty()) { message = i18np("Face tag '%2' is assigned to at least one item. " "Do you want to continue?", "Face tags '%2' are assigned to at least one item. " "Do you want to continue?", tagsWithImagesCount, tagsWithImages); } else { message = i18np("Remove face tag?", "Remove face tags?", tags.size()); } bool removeFaceTag = QMessageBox::Yes == (QMessageBox::warning(qApp->activeWindow(), qApp->applicationName(), message, QMessageBox::Yes | QMessageBox::Cancel)); if (removeFaceTag) { // Now we ask the user if we should also remove the tags from the images. QString msg = i18np("Remove the tag corresponding to this face tag from the images?", "Remove the %1 tags corresponding to this face tags from the images?", allPersonTagsToDelete.size()); bool removeTagFromImages = QMessageBox::Yes == (QMessageBox::warning(qApp->activeWindow(), qApp->applicationName(), msg, QMessageBox::Yes | QMessageBox::No)); MetadataHub metadataHub; // remove the face region from images and unassign the tag if wished foreach(const qlonglong& imageId, allAssignedItems) { foreach (TAlbum* const tagToRemove, allPersonTagsToDelete) { ImageTagPair imageTagAssociation(imageId,tagToRemove->id()); if (imageTagAssociation.isAssigned()) { imageTagAssociation.removeProperties(ImageTagPropertyName::autodetectedFace()); imageTagAssociation.removeProperties(ImageTagPropertyName::tagRegion()); if (removeTagFromImages) { imageTagAssociation.unAssignTag(); // Load the current metadata and sync the tags ImageInfo info(imageId); if (!info.isNull()) { metadataHub.load(info); if (!metadataHub.writeToMetadata(info)) { qCWarning(DIGIKAM_GENERAL_LOG) << "Failed writing tags to image " << info.filePath(); } } } } } } foreach(TAlbum* const tAlbum, allPersonTagsToDelete) { TagProperties props(tAlbum->id()); // Delete TagPropertyName::person() and TagPropertyName::faceEngineName() // fetch the UUID to delete the identity from facesdb props.removeProperties(TagPropertyName::person()); props.removeProperties(TagPropertyName::faceEngineName()); QString uuid = props.value(TagPropertyName::faceEngineUuid()); qCDebug(DIGIKAM_GENERAL_LOG) << "Deleting person tag properties for tag " << tAlbum->title() << " with uuid " << uuid; if (!uuid.isEmpty()) { // Delete the UUID props.removeProperties(TagPropertyName::faceEngineUuid()); // delete the faces db identity with this uuid. FaceDbAccess access; access.db()->deleteIdentity(uuid); } } } } void TagModificationHelper::slotMultipleFaceTagDel() { QList lst = boundMultipleTags(sender()); qCDebug(DIGIKAM_GENERAL_LOG) << lst.size(); slotMultipleFaceTagDel(lst); } void TagModificationHelper::slotTagToFaceTag(TAlbum* tAlbum) { if (!tAlbum) { return; } if (!FaceTags::isPerson(tAlbum->id())) { FaceTags::ensureIsPerson(tAlbum->id()); } } void TagModificationHelper::slotTagToFaceTag() { slotTagToFaceTag(boundTag(sender())); } void TagModificationHelper::slotMultipleTagsToFaceTags(QList& tags) { foreach(TAlbum* const selectedTag, tags) { slotTagToFaceTag(selectedTag); } } void TagModificationHelper::slotMultipleTagsToFaceTags() { QList lst = boundMultipleTags(sender()); qCDebug(DIGIKAM_GENERAL_LOG) << lst.size(); slotMultipleTagsToFaceTags(lst); } QList TagModificationHelper::getFaceTags(TAlbum* rootTag) { if (!rootTag) { return QList(); } QList tags; tags.append(rootTag); return getFaceTags(tags).toList(); } QSet TagModificationHelper::getFaceTags(QList tags) { QSet faceTags; foreach(TAlbum* const tAlbum, tags) { if (FaceTags::isPerson(tAlbum->id())) { faceTags.insert(tAlbum); } AlbumPointer tag(tAlbum); AlbumIterator iter(tag); // Get all shild tags which have the person property. while (iter.current()) { Album* const album = iter.current(); // Make sure that no nullp pointer dereference is done. // though while(iter.current()) already tests for the current // album being true, i.e. > 0 , i.e. non-null if (album) { TAlbum* const tAlbum = dynamic_cast(album); if (tAlbum && FaceTags::isPerson(tAlbum->id())) { faceTags.insert(tAlbum); } ++iter; } } } return faceTags; } } // namespace Digikam diff --git a/core/libs/tags/tagmodificationhelper.h b/core/libs/tags/tagmodificationhelper.h index 2aff6c3e85..d19886268b 100644 --- a/core/libs/tags/tagmodificationhelper.h +++ b/core/libs/tags/tagmodificationhelper.h @@ -1,237 +1,237 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2000-12-05 * Description : helper class used to modify tag albums in views * * Copyright (C) 2009-2010 by Johannes Wienke * Copyright (C) 2010-2018 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. * * ============================================================ */ #ifndef DIGIKAM_TAG_MODIFICATION_HELPER_H #define DIGIKAM_TAG_MODIFICATION_HELPER_H // Qt includes #include #include // Local includes #include "album.h" class QAction; namespace Digikam { /** * Utility class providing methods to modify tag albums (TAlbum) in a way * useful to implement views. * * This class can do background processing for batch tag operations. So be sure * that the signals indicating the progress of these operations are used. * * @author jwienke */ class TagModificationHelper: public QObject { Q_OBJECT public: /** * Constructor. * * @param parent parent for qt parent child mechanism * @param dialogParent paret widget for dialogs displayed by this object */ explicit TagModificationHelper(QObject* const parent, QWidget* const dialogParent); /** * Destructor. */ virtual ~TagModificationHelper(); public Q_SLOTS: /** * Creates one ore more new tags under the given parent. If only the parent * is given, then a dialog is shown to create new tags. Else, if also a * title and optionally an icon are given, then these values will be used * directly to create the tag. * * @param parent parent tag album under which to create the new tags. May be * 0 to use the root album * @param title if this isn't an empty string, then this tag name is * suggested * @param iconName an optional name for the icon to suggest for the new tag * @return new tag album or 0 if not created */ TAlbum* slotTagNew(TAlbum* parent, const QString& title = QString(), const QString& iconName = QString()); /** * Same as above, but this slot can be triggered from a QAction * if a parent tag is bound to this action, see below. * Without this mechanism, will add a toplevel tag. * * @return new tag created or 0 if no tag was created */ TAlbum* slotTagNew(); /** * Edits the given tag via a user dialog. * * @param tag the tag to change */ void slotTagEdit(TAlbum* tag); void slotTagEdit(); /// must use bindTag and a QAction /** * Deletes the given tag and after prompting the user for this. * * @param tag the tag to delete, must not be the root tag album */ void slotTagDelete(TAlbum* tag); /** * must use bindTag and a QAction */ void slotTagDelete(); /// must use bindTag and a QAction /** * Delete multiple tags and prompt user only once for all * * @param tags tags to be deleted, without root tag */ - void slotMultipleTagDel(QList& tags); + void slotMultipleTagDel(QList& tags); /** * must use bindMultipleTags and a QAction */ void slotMultipleTagDel(); /** * Deletes the given face tag and after prompting the user for this. * The tag itself is not deleted. Only its property as face tag. * * @param tag the face tag to delete */ void slotFaceTagDelete(TAlbum* tag); /** * must use bindTag and a QAction */ void slotFaceTagDelete(); /// must use bindTag and a QAction /** * Delete multiple face tags and prompt user only once for all * The tags itself are not deleted. Only their properties as face tags. * * @param tags face tags to be deleted. */ - void slotMultipleFaceTagDel(QList& tags); + void slotMultipleFaceTagDel(QList& tags); /** * must use bindMultipleTags and a QAction */ void slotMultipleFaceTagDel(); /** * Marks the tag as face tag if it is not already. * * @param tag the tag to mark */ void slotTagToFaceTag(TAlbum* tag); /** * must use bindTag and a QAction */ void slotTagToFaceTag(); /// must use bindTag and a QAction /** * Marks the tags as face tags if they are not already. * * @param tags the tags to mark. */ - void slotMultipleTagsToFaceTags(QList& tags); + void slotMultipleTagsToFaceTags(QList& tags); /** * must use bindMultipleTags and a QAction */ void slotMultipleTagsToFaceTags(); /** * Sets the tag that the given action operates on. * You must call bindTag and then connect the action's triggered * to the desired slot, slotTagNew(), slotTagEdit() or slotTagDelete(). * Note: Changes the Action's user data. */ void bindTag(QAction* action, TAlbum* parent) const; /** * Returns the tag bound with bindTag. The given QObject shall be * a QAction, but for convenience the given object * will be checked with qobject_cast first, so you can pass QObject::sender(). */ TAlbum* boundTag(QObject* action) const; /** * Set QVector's pointer into action's data. Make sure that QVector is not * a local object and it's not destroyed before boundMultipleTags are called * * @param action - action to store pointer * @param tags - QVector pointer to be stored */ void bindMultipleTags(QAction* action, QList tags); /** * Return QVector pointer bound with bindMultipleTags. Use when context menu * should delete more than one item: multiple-selection. */ QList< TAlbum* > boundMultipleTags(QObject* sender); Q_SIGNALS: void tagCreated(TAlbum* tag); void tagEdited(TAlbum* tag); void aboutToDeleteTag(TAlbum* tag); private: /** * Returns all sub-tags of the given one which have the person property. * This includes the root tag, if it has the property, too. */ QList getFaceTags(TAlbum* rootTags = 0); /** * Returns all sub-tags of the given ones which have the person property. * This includes the root tags, if they have the property, too. */ QSet getFaceTags(QList tags); private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_TAG_MODIFICATION_HELPER_H diff --git a/core/libs/tags/tagsmanager/taglist.cpp b/core/libs/tags/tagsmanager/taglist.cpp index 1118a6a617..e18c6750ab 100644 --- a/core/libs/tags/tagsmanager/taglist.cpp +++ b/core/libs/tags/tagsmanager/taglist.cpp @@ -1,289 +1,289 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 20013-07-31 * Description : Tag List implementation as Quick Access for various * subtrees in Tag Manager * * Copyright (C) 2013 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 "taglist.h" // Qt includes #include #include #include // Local includes #include "digikam_debug.h" #include "albumtreeview.h" #include "tagmngrtreeview.h" #include "tagmngrlistmodel.h" #include "tagmngrlistview.h" #include "tagmngrlistitem.h" namespace Digikam { class TagList::Private { public: explicit Private() { addButton = 0; tagList = 0; tagListModel = 0; treeView = 0; } QPushButton* addButton; TagMngrListView* tagList; TagMngrListModel* tagListModel; TagMngrTreeView* treeView; QMap > tagMap; }; TagList::TagList(TagMngrTreeView* const treeView, QWidget* const parent) : QWidget(parent), d(new Private()) { d->treeView = treeView; QVBoxLayout* const layout = new QVBoxLayout(); d->addButton = new QPushButton(i18n("Add to List")); d->addButton->setToolTip(i18n("Add selected tags to Quick Access List")); d->tagList = new TagMngrListView(this); d->tagListModel = new TagMngrListModel(this); d->tagList->setModel(d->tagListModel); d->tagList->setSelectionMode(QAbstractItemView::ExtendedSelection); d->tagList->setDragEnabled(true); d->tagList->setAcceptDrops(true); d->tagList->setDropIndicatorShown(true); layout->addWidget(d->addButton); layout->addWidget(d->tagList); connect(d->addButton, SIGNAL(clicked()), this, SLOT(slotAddPressed())); connect(d->tagList->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(slotSelectionChanged())); connect(AlbumManager::instance(), SIGNAL(signalAlbumDeleted(Album*)), this, SLOT(slotTagDeleted(Album*))); restoreSettings(); this->setLayout(layout); } TagList::~TagList() { delete d->tagList; delete d->tagListModel; delete d; } void TagList::saveSettings() { KConfig conf(QLatin1String("digikam_tagsmanagerrc")); conf.deleteGroup(QLatin1String("List Content")); KConfigGroup group = conf.group(QLatin1String("List Content")); QList currentItems = d->tagListModel->allItems(); group.writeEntry(QLatin1String("Size"), currentItems.count()-1); - for (int it = 1; it < currentItems.size(); it++) + for (int it = 1 ; it < currentItems.size() ; ++it) { QList ids = currentItems.at(it)->getTagIds(); QString saveData; - for (int jt = 0; jt < ids.size(); jt++) + for (int jt = 0 ; jt < ids.size() ; ++jt) { - saveData.append(QString::number(ids.at(jt)) + QLatin1String(" ")); + saveData.append(QString::number(ids.at(jt)) + QLatin1Char(' ')); } group.writeEntry(QString::fromUtf8("item%1").arg(it-1), saveData); } } void TagList::restoreSettings() { KConfig conf(QLatin1String("digikam_tagsmanagerrc")); KConfigGroup group = conf.group(QLatin1String("List Content")); QStringList items; int size = group.readEntry(QLatin1String("Size"), -1); /** * If config is empty add generic All Tags */ d->tagListModel->addItem(QList() << QBrush(Qt::cyan, Qt::Dense2Pattern)); if (size == 0 || size < 0) { return; } - for(int it = 0; it < size; it++) + for (int it = 0 ; it < size ; ++it) { QString data = group.readEntry(QString::fromUtf8("item%1").arg(it), ""); - if(data.isEmpty()) + if (data.isEmpty()) continue; QStringList ids = data.split(QLatin1String(" "), QString::SkipEmptyParts); QList itemData; itemData << QBrush(Qt::cyan, Qt::Dense2Pattern); foreach(const QString& tagId, ids) { TAlbum* const item = AlbumManager::instance()->findTAlbum(tagId.toInt()); - if(item) + if (item) { itemData << item->id(); } } ListItem* const listItem = d->tagListModel->addItem(itemData); /** Use this map to find all List Items that contain specific tag * usually to remove deleted tag */ foreach(int tagId, listItem->getTagIds()) { d->tagMap[tagId].append(listItem); } } /** "All Tags" item should be selected **/ QModelIndex rootIndex = d->tagList->model()->index(0,0); d->tagList->setCurrentIndex(rootIndex); } void TagList::slotAddPressed() { QModelIndexList selected = d->treeView->selectionModel()->selectedIndexes(); if (selected.isEmpty()) { return; } QList itemData; itemData << QBrush(Qt::cyan, Qt::Dense2Pattern); foreach(const QModelIndex& index, selected) { TAlbum* const album = static_cast(d->treeView->albumForIndex(index)); itemData << album->id(); } ListItem* listItem = d->tagListModel->addItem(itemData); /** Use this map to find all List Items that contain specific tag * usually to remove deleted tag */ foreach(int tagId, listItem->getTagIds()) { d->tagMap[tagId].append(listItem); } } void TagList::slotSelectionChanged() { QModelIndexList indexList = d->tagList->mySelectedIndexes(); QSet mySet; foreach(const QModelIndex& index, indexList) { ListItem* const item = static_cast(index.internalPointer()); if (item->getTagIds().isEmpty()) { mySet.clear(); break; } foreach(int tagId, item->getTagIds()) { mySet.insert(tagId); } } TagsManagerFilterModel* const filterModel = d->treeView->getFilterModel(); filterModel->setQuickListTags(QList::fromSet(mySet)); } void TagList::slotTagDeleted(Album* album) { TAlbum* const talbum = dynamic_cast(album); if (!talbum) { return; } int delId = talbum->id(); QList items = d->tagMap[delId]; foreach(ListItem* const item, items) { item->removeTagId(delId); if (item->getTagIds().isEmpty()) { d->tagListModel->deleteItem(item); d->tagMap[delId].removeOne(item); d->treeView->getFilterModel()->setQuickListTags(QList()); } } } void TagList::slotDeleteSelected() { QModelIndexList sel = d->tagList->selectionModel()->selectedIndexes(); if (sel.isEmpty()) { return; } foreach(const QModelIndex& index, sel) { ListItem* const item = static_cast(index.internalPointer()); d->tagListModel->deleteItem(item); } d->tagList->selectionModel()->select(d->tagList->model()->index(0,0), QItemSelectionModel::SelectCurrent); } void TagList::enableAddButton(bool value) { d->addButton->setEnabled(value); } } // namespace Digikam diff --git a/core/showfoto/thumbbar/showfotothumbnailmodel.cpp b/core/showfoto/thumbbar/showfotothumbnailmodel.cpp index 50bd528b9d..004f4e5acd 100644 --- a/core/showfoto/thumbbar/showfotothumbnailmodel.cpp +++ b/core/showfoto/thumbbar/showfotothumbnailmodel.cpp @@ -1,371 +1,371 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2013-07-22 * Description : Qt item model for Showfoto thumbnails entries * * Copyright (C) 2013 by Mohamed_Anwer * * 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 "showfotothumbnailmodel.h" // Local includes #include "drawdecoder.h" #include "digikam_debug.h" #include "dmetadata.h" #include "imagescanner.h" #include "thumbnailsize.h" #include "thumbnailloadthread.h" #include "loadingdescription.h" using namespace Digikam; namespace ShowFoto { class ShowfotoThumbnailModel::Private { public: explicit Private() : thread(0), preloadThread(0), thumbSize(0), lastGlobalThumbSize(0), preloadThumbSize(0), emitDataChanged(true) { maxThumbSize = ThumbnailSize::Huge; } int preloadThumbnailSize() const { if (preloadThumbSize.size()) { return preloadThumbSize.size(); } return thumbSize.size(); } public: ThumbnailLoadThread* thread; ThumbnailLoadThread* preloadThread; ThumbnailSize thumbSize; ThumbnailSize lastGlobalThumbSize; ThumbnailSize preloadThumbSize; QRect detailRect; int maxThumbSize; bool emitDataChanged; }; ShowfotoThumbnailModel::ShowfotoThumbnailModel(QObject* const parent) : ShowfotoImageModel(parent), d(new Private) { connect(this, &ShowfotoThumbnailModel::signalThumbInfo, this, &ShowfotoThumbnailModel::slotThumbInfoLoaded); } ShowfotoThumbnailModel::~ShowfotoThumbnailModel() { delete d->preloadThread; delete d; } void ShowfotoThumbnailModel::setThumbnailLoadThread(ThumbnailLoadThread* thread) { d->thread = thread; connect(d->thread, &ThumbnailLoadThread::signalThumbnailLoaded, this, &ShowfotoThumbnailModel::slotThumbnailLoaded); } ThumbnailLoadThread* ShowfotoThumbnailModel::thumbnailLoadThread() const { return d->thread; } ThumbnailSize ShowfotoThumbnailModel::thumbnailSize() const { return d->thumbSize; } void ShowfotoThumbnailModel::setThumbnailSize(const ThumbnailSize& size) { d->lastGlobalThumbSize = size; d->thumbSize = size; } void ShowfotoThumbnailModel::setPreloadThumbnailSize(const ThumbnailSize& size) { d->preloadThumbSize = size; } void ShowfotoThumbnailModel::setEmitDataChanged(bool emitSignal) { d->emitDataChanged = emitSignal; } void ShowfotoThumbnailModel::showfotoItemInfosCleared() { if (d->preloadThread) { d->preloadThread->stopAllTasks(); } } QVariant ShowfotoThumbnailModel::data(const QModelIndex& index, int role) const { if (role == ThumbnailRole && d->thread && index.isValid()) { QImage thumbnailImage; QPixmap pixmap; ShowfotoItemInfo info = showfotoItemInfo(index); QString url = info.url.toDisplayString(); - QString path = info.folder + QLatin1String("/") + info.name; + QString path = info.folder + QLatin1Char('/') + info.name; if (info.isNull() || url.isEmpty()) { return QVariant(QVariant::Pixmap); } if(pixmapForItem(path,pixmap)) { return pixmap; } //if pixmapForItem Failed if(getThumbnail(info,thumbnailImage)) { thumbnailImage = thumbnailImage.scaled(d->thumbSize.size(),d->thumbSize.size(),Qt::KeepAspectRatio); emit signalThumbInfo(info,thumbnailImage); return thumbnailImage; } return QVariant(QVariant::Pixmap); } return ShowfotoImageModel::data(index, role); } bool ShowfotoThumbnailModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (role == ThumbnailRole) { switch (value.type()) { case QVariant::Invalid: d->thumbSize = d->lastGlobalThumbSize; d->detailRect = QRect(); break; case QVariant::Int: if (value.isNull()) { d->thumbSize = ThumbnailSize(d->lastGlobalThumbSize); } else { d->thumbSize = ThumbnailSize(value.toInt()); } break; case QVariant::Rect: if (value.isNull()) { d->detailRect = QRect(); } else { d->detailRect = value.toRect(); } break; default: break; } } return ShowfotoImageModel::setData(index, value, role); } void ShowfotoThumbnailModel::slotThumbnailLoaded(const LoadingDescription& loadingDescription, const QPixmap& thumb) { if (thumb.isNull()) { return; } // In case of multiple occurrence, we currently do not know which thumbnail is this. Signal change on all. foreach(const QModelIndex& index, indexesForUrl(QUrl::fromLocalFile(loadingDescription.filePath))) { if (thumb.isNull()) { emit thumbnailFailed(index, loadingDescription.previewParameters.size); } else { emit thumbnailAvailable(index, loadingDescription.previewParameters.size); if (d->emitDataChanged) { emit dataChanged(index, index); } } } } bool ShowfotoThumbnailModel::getThumbnail(const ShowfotoItemInfo& itemInfo, QImage& thumbnail) const { - QString path = itemInfo.folder + QLatin1String("/") + itemInfo.name; + QString path = itemInfo.folder + QLatin1Char('/') + itemInfo.name; // Try to get preview from Exif data (good quality). Can work with Raw files DMetadata metadata(path); metadata.getImagePreview(thumbnail); if (!thumbnail.isNull()) { return true; } // RAW files : try to extract embedded thumbnail using RawEngine DRawDecoder::loadRawPreview(thumbnail, path); if (!thumbnail.isNull()) { return true; } KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(QLatin1String("Camera Settings")); bool turnHighQualityThumbs = group.readEntry(QLatin1String("TurnHighQualityThumbs"), false); // Try to get thumbnail from Exif data (poor quality). if (!turnHighQualityThumbs) { thumbnail = metadata.getExifThumbnail(true); if (!thumbnail.isNull()) { return true; } } // THM files: try to get thumbnail from '.thm' files if we didn't manage to get // thumbnail from Exif. Any cameras provides *.thm files like JPEG files with RAW files. // Using this way is always speed up than ultimate loading using DImg. // Note: the thumbnail extracted with this method can be in poor quality. // 2006/27/01 - Gilles - Tested with my Minolta Dynax 5D USM camera. QFileInfo fi(path); - if (thumbnail.load(itemInfo.folder + QLatin1String("/") + fi.baseName() + QLatin1String(".thm"))) // Lowercase + if (thumbnail.load(itemInfo.folder + QLatin1Char('/') + fi.baseName() + QLatin1String(".thm"))) // Lowercase { if (!thumbnail.isNull()) { return true; } } - else if (thumbnail.load(itemInfo.folder + QLatin1String("/") + fi.baseName() + QLatin1String(".THM"))) // Uppercase + else if (thumbnail.load(itemInfo.folder + QLatin1Char('/') + fi.baseName() + QLatin1String(".THM"))) // Uppercase { if (!thumbnail.isNull()) { return true; } } // Finally, we trying to get thumbnail using DImg API (slow). // qCDebug(DIGIKAM_SHOWFOTO_LOG) << "Use DImg loader to get thumbnail from : " << path; // DImg dimgThumb(path); // if (!dimgThumb.isNull()) // { // thumbnail = dimgThumb.copyQImage(); // return true; // } return false; } bool ShowfotoThumbnailModel::pixmapForItem(QString url, QPixmap& pix) const { if (d->thumbSize.size() > d->maxThumbSize) { //TODO: Install a widget maximum size to prevent this situation bool hasPixmap = d->thread->find(ThumbnailIdentifier(url), pix, d->maxThumbSize); if (hasPixmap) { qCWarning(DIGIKAM_GENERAL_LOG) << "Thumbbar: Requested thumbnail size" << d->thumbSize.size() << "is larger than the maximum thumbnail size" << d->maxThumbSize << ". Returning a scaled-up image."; pix = pix.scaled(d->thumbSize.size(), d->thumbSize.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); return true; } else { return false; } } else { return d->thread->find(ThumbnailIdentifier(url), pix, d->thumbSize.size()); } } void ShowfotoThumbnailModel::slotThumbInfoLoaded(const ShowfotoItemInfo& info, const QImage& thumbnailImage) { QImage thumbnail = thumbnailImage; if (thumbnail.isNull()) { thumbnail = QImage(); } foreach(const QModelIndex& index, indexesForUrl(info.url)) { if (thumbnail.isNull()) { emit thumbnailFailed(index, d->thumbSize.size()); } else { emit thumbnailAvailable(index, d->thumbSize.size()); if (d->emitDataChanged) { emit dataChanged(index, index); } } } } } // namespace ShowFoto diff --git a/core/tests/albummodel/albummodeltest.cpp b/core/tests/albummodel/albummodeltest.cpp index 7fe9cfa43f..ae82257b71 100644 --- a/core/tests/albummodel/albummodeltest.cpp +++ b/core/tests/albummodel/albummodeltest.cpp @@ -1,811 +1,811 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2009-12-11 * Description : test cases for the various album models * * Copyright (C) 2009 by Johannes Wienke * * 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 "albummodeltest.h" // Qt includes #include #include #include #include // Local includes #include "albumfiltermodel.h" #include "albummanager.h" #include "albummodel.h" #include "applicationsettings.h" #include "albumthumbnailloader.h" #include "collectionlocation.h" #include "collectionmanager.h" #include "loadingcacheinterface.h" #include "scancontroller.h" #include "thumbnailloadthread.h" #include "../modeltest/modeltest.h" using namespace Digikam; const QString IMAGE_PATH(QFINDTESTDATA("data/")); QTEST_MAIN(AlbumModelTest) AlbumModelTest::AlbumModelTest() : albumCategory(QLatin1String("DummyCategory")), palbumRoot0(0), palbumRoot1(0), palbumRoot2(0), palbumChild0Root0(0), palbumChild1Root0(0), palbumChild2Root0(0), palbumChild0Root1(0), rootTag(0), talbumRoot0(0), talbumRoot1(0), talbumChild0Root0(0), talbumChild1Root0(0), talbumChild0Child1Root0(0), talbumChild0Root1(0), startModel(0) { } AlbumModelTest::~AlbumModelTest() { } /** * TODO: this test case needs to be removed, since it depends on database, * threading etc, therefore it is not predictable and very hard to fix */ void AlbumModelTest::initTestCase() { tempSuffix = QLatin1String("albummodeltest-") + QTime::currentTime().toString(); - dbPath = QDir::temp().absolutePath() + QLatin1String("/") + tempSuffix; + dbPath = QDir::temp().absolutePath() + QLatin1Char('/') + tempSuffix; if (QDir::temp().exists(tempSuffix)) { QString msg = QLatin1String("Error creating temp path") + dbPath; QVERIFY2(false, msg.toLatin1().constData()); } QDir::temp().mkdir(tempSuffix); qDebug() << "Using database path for test: " << dbPath; ApplicationSettings::instance()->setShowFolderTreeViewItemsCount(true); // use a testing database AlbumManager::instance(); // catch palbum counts for waiting connect(AlbumManager::instance(), SIGNAL(signalPAlbumsDirty(QMap)), this, SLOT(setLastPAlbumCountMap(QMap))); AlbumManager::checkDatabaseDirsAfterFirstRun(QDir::temp().absoluteFilePath( tempSuffix), QDir::temp().absoluteFilePath(tempSuffix)); DbEngineParameters params(QLatin1String("QSQLITE"), QDir::temp().absoluteFilePath(tempSuffix + QLatin1String("/digikam4.db")), QString(), QString(), -1, false, QString(), QString()); bool dbChangeGood = AlbumManager::instance()->setDatabase(params, false, QDir::temp().absoluteFilePath(tempSuffix)); QVERIFY2(dbChangeGood, "Could not set temp album db"); QList locs = CollectionManager::instance()->allAvailableLocations(); QVERIFY2(locs.size(), "Failed to auto-create one collection in setDatabase"); ScanController::instance()->completeCollectionScan(); AlbumManager::instance()->startScan(); AlbumList all = AlbumManager::instance()->allPAlbums(); qDebug() << "PAlbum registered : " << all.size(); foreach(Album* const a, all) { if (a) { qDebug() << " ==> Id : " << a->id() << " , is root : " << a->isRoot() << " , title : " << a->title(); } } QVERIFY2( all.size() == 3, "Failed to scan empty directory. We must have one root album, one album, and one trash."); } void AlbumModelTest::cleanupTestCase() { qDebug() << "Start AlbumModelTest::cleanupTestCase()"; ScanController::instance()->shutDown(); AlbumManager::instance()->cleanUp(); ThumbnailLoadThread::cleanUp(); AlbumThumbnailLoader::instance()->cleanUp(); LoadingCacheInterface::cleanUp(); QDir dir(dbPath); dir.removeRecursively(); qDebug() << "deleted test folder " << dbPath; } // Qt test doesn't use exceptions, so using assertion macros in methods called // from a test slot doesn't stop the test method and may result in inconsistent // data or segfaults. Therefore use macros for these functions. #define safeCreatePAlbum(parent, name, result) \ { \ QString error; \ result = AlbumManager::instance()->createPAlbum(parent, name, name, \ QDate::currentDate(), albumCategory, error); \ QVERIFY2(result, QString::fromUtf8("Error creating PAlbum for test: %1").arg(error).toLatin1().constData()); \ } #define safeCreateTAlbum(parent, name, result) \ { \ QString error; \ result = AlbumManager::instance()->createTAlbum(parent, name, QLatin1String(""), error); \ QVERIFY2(result, QString::fromUtf8("Error creating TAlbum for test: %1").arg(error).toLatin1().constData()); \ } void AlbumModelTest::init() { qDebug() << "Start AlbumModelTest::init()"; palbumCountMap.clear(); // create a model to check that model work is done correctly while scanning addedIds.clear(); startModel = new AlbumModel; startModel->setShowCount(true); connect(startModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(slotStartModelRowsInserted(QModelIndex,int,int))); connect(startModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(slotStartModelDataChanged(QModelIndex,QModelIndex))); qDebug() << "Created startModel" << startModel; // ensure that this model is empty in the beginning except for the root // album and the collection that include trash QCOMPARE(startModel->rowCount(), 1); QModelIndex rootIndex = startModel->index(0, 0); QCOMPARE(startModel->rowCount(rootIndex), 1); QModelIndex collectionIndex = startModel->index(0, 0, rootIndex); QCOMPARE(startModel->rowCount(collectionIndex), 1); // insert some test data // physical albums // create two of them by creating directories and scanning QDir dir(dbPath); dir.mkdir(QLatin1String("root0")); dir.mkdir(QLatin1String("root1")); ScanController::instance()->completeCollectionScan(); AlbumManager::instance()->refresh(); QCOMPARE(AlbumManager::instance()->allPAlbums().size(), 5); QString error; palbumRoot0 = AlbumManager::instance()->findPAlbum(QUrl::fromLocalFile(dbPath + QLatin1String("/root0"))); QVERIFY2(palbumRoot0, "Error having PAlbum root0 in AlbumManager"); palbumRoot1 = AlbumManager::instance()->findPAlbum(QUrl::fromLocalFile(dbPath + QLatin1String("/root1"))); QVERIFY2(palbumRoot1, "Error having PAlbum root1 in AlbumManager"); // Create some more through AlbumManager palbumRoot2 = AlbumManager::instance()->createPAlbum(dbPath, QLatin1String("root2"), QLatin1String("root album 2"), QDate::currentDate(), albumCategory, error); QVERIFY2(palbumRoot2, QString::fromUtf8("Error creating PAlbum for test: %1").arg(error).toLatin1().constData()); safeCreatePAlbum(palbumRoot0, QLatin1String("root0child0"), palbumChild0Root0); safeCreatePAlbum(palbumRoot0, QLatin1String("root0child1"), palbumChild1Root0); const QString sameName = QLatin1String("sameName Album"); safeCreatePAlbum(palbumRoot0, sameName, palbumChild2Root0); safeCreatePAlbum(palbumRoot1, sameName, palbumChild0Root1); qDebug() << "AlbumManager now knows these PAlbums:"; foreach(Album* const a, AlbumManager::instance()->allPAlbums()) { qDebug() << "\t" << a->title(); } // tags rootTag = AlbumManager::instance()->findTAlbum(0); QVERIFY(rootTag); safeCreateTAlbum(rootTag, QLatin1String("root0"), talbumRoot0); safeCreateTAlbum(rootTag, QLatin1String("root1"), talbumRoot1); safeCreateTAlbum(talbumRoot0, QLatin1String("child0 root 0"), talbumChild0Root0); safeCreateTAlbum(talbumRoot0, QLatin1String("child1 root 0"), talbumChild1Root0); safeCreateTAlbum(talbumChild1Root0, sameName, talbumChild0Child1Root0); safeCreateTAlbum(talbumRoot1, sameName, talbumChild0Root1); qDebug() << "created tags"; // add some images for having date albums QDir imageDir(IMAGE_PATH); imageDir.setNameFilters(QStringList() << QLatin1String("*.jpg")); QStringList imageFiles = imageDir.entryList(); qDebug() << "copying images " << imageFiles << " to " << palbumChild0Root0->fileUrl(); foreach(const QString& imageFile, imageFiles) { QString src = IMAGE_PATH + QLatin1Char('/') + imageFile; QString dst = palbumChild0Root0->fileUrl().toLocalFile() + QLatin1Char('/') + imageFile; bool copied = QFile::copy(src, dst); QVERIFY2(copied, "Test images must be copied"); } ScanController::instance()->completeCollectionScan(); if (AlbumManager::instance()->allDAlbums().count() <= 1) { ensureItemCounts(); } qDebug() << "date albums: " << AlbumManager::instance()->allDAlbums(); // root + 2 years + 2 and 3 months per year + (1997 as test year for date ordering with 12 months) = 21 QCOMPARE(AlbumManager::instance()->allDAlbums().size(), 21); // ensure that there is a root date album DAlbum* const rootFromAlbumManager = AlbumManager::instance()->findDAlbum(0); QVERIFY(rootFromAlbumManager); DAlbum* rootFromList = 0; foreach(Album* const album, AlbumManager::instance()->allDAlbums()) { DAlbum* const dAlbum = dynamic_cast (album); QVERIFY(dAlbum); if (dAlbum->isRoot()) { rootFromList = dAlbum; } } QVERIFY(rootFromList); QVERIFY(rootFromList == rootFromAlbumManager); } void AlbumModelTest::testStartAlbumModel() { qDebug() << "Start AlbumModelTest::testStartAlbumModel()"; // verify that the start album model got all these changes // one root QCOMPARE(startModel->rowCount(), 1); // one collection QModelIndex rootIndex = startModel->index(0, 0); QCOMPARE(startModel->rowCount(rootIndex), 1); // two albums in the collection QModelIndex collectionIndex = startModel->index(0, 0, rootIndex); QCOMPARE(startModel->rowCount(collectionIndex), 3); // this is should be enough for now // We must have received an added notation for everything except album root // and collection QCOMPARE(addedIds.size(), 7); } void AlbumModelTest::ensureItemCounts() { // trigger listing job QEventLoop dAlbumLoop; connect(AlbumManager::instance(), SIGNAL(signalAllDAlbumsLoaded()), &dAlbumLoop, SLOT(quit())); AlbumManager::instance()->prepareItemCounts(); qDebug() << "Waiting for AlbumManager and the IOSlave to create DAlbums..."; dAlbumLoop.exec(); qDebug() << "DAlbums were created"; while (palbumCountMap.size() < 8) { QEventLoop pAlbumLoop; connect(AlbumManager::instance(), SIGNAL(signalPAlbumsDirty(QMap)), &pAlbumLoop, SLOT(quit())); qDebug() << "Waiting for first PAlbum count map"; pAlbumLoop.exec(); qDebug() << "Got new PAlbum count map"; } } void AlbumModelTest::slotStartModelRowsInserted(const QModelIndex& parent, int start, int end) { qDebug() << "called, parent:" << parent << ", start:" << start << ", end:" << end; for (int row = start; row <= end; ++row) { QModelIndex child = startModel->index(row, 0, parent); QVERIFY(child.isValid()); Album* album = startModel->albumForIndex(child); const int id = child.data(AbstractAlbumModel::AlbumIdRole).toInt(); QVERIFY(album); qDebug() << "added album with id" << id << "and name" << album->title(); addedIds << id; } } void AlbumModelTest::slotStartModelDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { for (int row = topLeft.row(); row <= bottomRight.row(); ++row) { QModelIndex index = startModel->index(row, topLeft.column(), topLeft.parent()); if (!index.isValid()) { qDebug() << "Illegal index received"; continue; } int albumId = index.data(AbstractAlbumModel::AlbumIdRole).toInt(); if (!addedIds.contains(albumId)) { QString message = QLatin1String("Album id ") + QString::number(albumId) + QLatin1String(" was changed before adding signal was received"); QFAIL(message.toLatin1().constData()); qDebug() << message; } } } void AlbumModelTest::deletePAlbum(PAlbum* album) { QDir dir(album->folderPath()); dir.removeRecursively(); } void AlbumModelTest::setLastPAlbumCountMap(const QMap &map) { qDebug() << "Receiving new count map "<< map; palbumCountMap = map; } void AlbumModelTest::cleanup() { if (startModel) { disconnect(startModel); } delete startModel; addedIds.clear(); // remove all test data AlbumManager::instance()->refresh(); // remove all palbums' directories deletePAlbum(palbumRoot0); deletePAlbum(palbumRoot1); deletePAlbum(palbumRoot2); // take over changes to database ScanController::instance()->completeCollectionScan(); // reread from database AlbumManager::instance()->refresh(); // root + one collection QCOMPARE(AlbumManager::instance()->allPAlbums().size(), 2); // remove all tags QString error; bool removed = AlbumManager::instance()->deleteTAlbum(talbumRoot0, error, false); QVERIFY2(removed, QString::fromUtf8("Error removing a tag: %1").arg(error).toLatin1().constData()); removed = AlbumManager::instance()->deleteTAlbum(talbumRoot1, error, false); QVERIFY2(removed, QString::fromUtf8("Error removing a tag: %1").arg(error).toLatin1().constData()); QCOMPARE(AlbumManager::instance()->allTAlbums().size(), 1); } void AlbumModelTest::testPAlbumModel() { qDebug() << "Start AlbumModelTest::testPAlbumModel()"; AlbumModel* albumModel = new AlbumModel(); ModelTest* test = new ModelTest(albumModel, 0); delete test; delete albumModel; albumModel = new AlbumModel(AbstractAlbumModel::IgnoreRootAlbum); test = new ModelTest(albumModel, 0); delete test; delete albumModel; } void AlbumModelTest::testDisablePAlbumCount() { qDebug() << "Start AlbumModelTest::testDisablePAlbumCount()"; AlbumModel albumModel; albumModel.setCountMap(palbumCountMap); albumModel.setShowCount(true); QRegExp countRegEx(QLatin1String(".+ \\(\\d+\\)")); countRegEx.setMinimal(true); QVERIFY(countRegEx.exactMatch(QLatin1String("test (10)"))); QVERIFY(countRegEx.exactMatch(QLatin1String("te st (10)"))); QVERIFY(countRegEx.exactMatch(QLatin1String("te st (0)"))); QVERIFY(!countRegEx.exactMatch(QLatin1String("te st ()"))); QVERIFY(!countRegEx.exactMatch(QLatin1String("te st"))); QVERIFY(!countRegEx.exactMatch(QLatin1String("te st (10) bla"))); // ensure that all albums except the root album have a count attached QModelIndex rootIndex = albumModel.index(0, 0, QModelIndex()); QString rootTitle = albumModel.data(rootIndex, Qt::DisplayRole).toString(); QVERIFY(!countRegEx.exactMatch(rootTitle)); for (int collectionRow = 0; collectionRow < albumModel.rowCount(rootIndex); ++collectionRow) { QModelIndex collectionIndex = albumModel.index(collectionRow, 0, rootIndex); QString collectionTitle = albumModel.data(collectionIndex, Qt::DisplayRole).toString(); QVERIFY2(countRegEx.exactMatch(collectionTitle), QString::fromUtf8("%1 matching error").arg(collectionTitle).toLatin1().constData()); for (int albumRow = 0; albumRow < albumModel.rowCount(collectionIndex); ++albumRow) { QModelIndex albumIndex = albumModel.index(albumRow, 0, collectionIndex); QString albumTitle = albumModel.data(albumIndex, Qt::DisplayRole).toString(); QVERIFY2(countRegEx.exactMatch(albumTitle), QString::fromUtf8("%1 matching error").arg(albumTitle).toLatin1().constData()); } } // now disable showing the count albumModel.setShowCount(false); // ensure that no album has a count attached rootTitle = albumModel.data(rootIndex, Qt::DisplayRole).toString(); QVERIFY(!countRegEx.exactMatch(rootTitle)); for (int collectionRow = 0; collectionRow < albumModel.rowCount(rootIndex); ++collectionRow) { QModelIndex collectionIndex = albumModel.index(collectionRow, 0, rootIndex); QString collectionTitle = albumModel.data(collectionIndex, Qt::DisplayRole).toString(); QVERIFY2(!countRegEx.exactMatch(collectionTitle), QString::fromUtf8("%1 matching error").arg(collectionTitle).toLatin1().constData()); for (int albumRow = 0; albumRow < albumModel.rowCount(collectionIndex); ++albumRow) { QModelIndex albumIndex = albumModel.index(albumRow, 0, collectionIndex); QString albumTitle = albumModel.data(albumIndex, Qt::DisplayRole).toString(); QVERIFY2(!countRegEx.exactMatch(albumTitle), QString::fromUtf8("%1 matching error").arg(albumTitle).toLatin1().constData()); } } } void AlbumModelTest::testDAlbumModel() { qDebug() << "Start AlbumModelTest::testDAlbumModel()"; DateAlbumModel* const albumModel = new DateAlbumModel(); ensureItemCounts(); ModelTest* const test = new ModelTest(albumModel, 0); delete test; delete albumModel; } void AlbumModelTest::testDAlbumContainsAlbums() { qDebug() << "Start AlbumModelTest::testDAlbumContainsAlbums()"; DateAlbumModel* const albumModel = new DateAlbumModel(); ensureItemCounts(); QVERIFY(albumModel->rootAlbum()); foreach(Album* const album, AlbumManager::instance()->allDAlbums()) { DAlbum* const dAlbum = dynamic_cast (album); QVERIFY(dAlbum); qDebug() << "checking album for date " << dAlbum->date() << ", range = " << dAlbum->range(); QModelIndex index = albumModel->indexForAlbum(dAlbum); if (!dAlbum->isRoot()) { QVERIFY(index.isValid()); } if (dAlbum->isRoot()) { // root album QVERIFY(dAlbum->isRoot()); QCOMPARE(albumModel->rowCount(index), 3); QCOMPARE(index, albumModel->rootAlbumIndex()); } else if (dAlbum->range() == DAlbum::Year && dAlbum->date().year() == 2007) { QCOMPARE(albumModel->rowCount(index), 2); } else if (dAlbum->range() == DAlbum::Year && dAlbum->date().year() == 2009) { QCOMPARE(albumModel->rowCount(index), 3); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2007 && dAlbum->date().month() == 3) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2007 && dAlbum->date().month() == 4) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2009 && dAlbum->date().month() == 3) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2009 && dAlbum->date().month() == 4) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2009 && dAlbum->date().month() == 5) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->date().year() == 1997) { // Ignore these albums for order testing } else { qDebug() << "Unexpected album: " << dAlbum->title(); QFAIL("Unexpected album returned from model"); } } delete albumModel; } void AlbumModelTest::testDAlbumSorting() { qDebug() << "Start AlbumModelTest::testDAlbumSorting()"; DateAlbumModel dateAlbumModel; AlbumFilterModel albumModel; albumModel.setSourceAlbumModel(&dateAlbumModel); // first check ascending order albumModel.sort(0, Qt::AscendingOrder); int previousYear = 0; for (int yearRow = 0; yearRow < albumModel.rowCount(); ++yearRow) { QModelIndex yearIndex = albumModel.index(yearRow, 0); DAlbum* const yearAlbum = dynamic_cast (albumModel.albumForIndex(yearIndex)); QVERIFY(yearAlbum); QVERIFY(yearAlbum->date().year() > previousYear); previousYear = yearAlbum->date().year(); int previousMonth = 0; for (int monthRow = 0; monthRow < albumModel.rowCount(yearIndex); ++monthRow) { QModelIndex monthIndex = albumModel.index(monthRow, 0, yearIndex); DAlbum* const monthAlbum = dynamic_cast (albumModel.albumForIndex(monthIndex)); QVERIFY(monthAlbum); QVERIFY(monthAlbum->date().month() > previousMonth); previousMonth = monthAlbum->date().month(); } } // then check descending order albumModel.sort(0, Qt::DescendingOrder); previousYear = 1000000; for (int yearRow = 0; yearRow < albumModel.rowCount(); ++yearRow) { QModelIndex yearIndex = albumModel.index(yearRow, 0); DAlbum* const yearAlbum = dynamic_cast (albumModel.albumForIndex(yearIndex)); QVERIFY(yearAlbum); QVERIFY(yearAlbum->date().year() < previousYear); previousYear = yearAlbum->date().year(); int previousMonth = 13; for (int monthRow = 0; monthRow < albumModel.rowCount(yearIndex); ++monthRow) { QModelIndex monthIndex = albumModel.index(monthRow, 0, yearIndex); DAlbum* const monthAlbum = dynamic_cast (albumModel.albumForIndex(monthIndex)); QVERIFY(monthAlbum); QVERIFY(monthAlbum->date().month() < previousMonth); previousMonth = monthAlbum->date().month(); } } } void AlbumModelTest::testDAlbumCount() { qDebug() << "Start AlbumModelTest::testDAlbumCount()"; DateAlbumModel* const albumModel = new DateAlbumModel(); albumModel->setShowCount(true); ensureItemCounts(); qDebug() << "iterating over root indices"; // check year albums for (int yearRow = 0; yearRow < albumModel->rowCount(albumModel->rootAlbumIndex()); ++yearRow) { QModelIndex yearIndex = albumModel->index(yearRow, 0); DAlbum* const yearDAlbum = albumModel->albumForIndex(yearIndex); QVERIFY(yearDAlbum); QVERIFY(yearDAlbum->range() == DAlbum::Year); if (yearDAlbum->date().year() == 2007) { const int imagesInYear = 7; albumModel->includeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), imagesInYear); albumModel->excludeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), 0); albumModel->includeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), imagesInYear); for (int monthRow = 0; monthRow < albumModel->rowCount(yearIndex); ++monthRow) { QModelIndex monthIndex = albumModel->index(monthRow, 0, yearIndex); DAlbum* monthDAlbum = albumModel->albumForIndex(monthIndex); QVERIFY(monthDAlbum); QVERIFY(monthDAlbum->range() == DAlbum::Month); QVERIFY(monthDAlbum->date().year() == 2007); if (monthDAlbum->date().month() == 3) { const int imagesInMonth = 3; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else if (monthDAlbum->date().month() == 4) { const int imagesInMonth = 4; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else { QFAIL("unexpected month album in 2007"); } } } else if (yearDAlbum->date().year() == 2009) { const int imagesInYear = 5; albumModel->includeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), imagesInYear); albumModel->excludeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), 0); albumModel->includeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), imagesInYear); for (int monthRow = 0; monthRow < albumModel->rowCount(yearIndex); ++monthRow) { QModelIndex monthIndex = albumModel->index(monthRow, 0, yearIndex); DAlbum* monthDAlbum = albumModel->albumForIndex(monthIndex); QVERIFY(monthDAlbum); QVERIFY(monthDAlbum->range() == DAlbum::Month); QVERIFY(monthDAlbum->date().year() == 2009); if (monthDAlbum->date().month() == 3) { const int imagesInMonth = 2; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else if (monthDAlbum->date().month() == 4) { const int imagesInMonth = 2; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else if (monthDAlbum->date().month() == 5) { const int imagesInMonth = 1; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else { QFAIL("unexpected month album in 2009"); } } } else if (yearDAlbum->date().year() == 1997) { // Nothing to do here, ignore the albums for ordering tests } else { QFAIL("Received unexpected album from model"); } } delete albumModel; } void AlbumModelTest::testTAlbumModel() { qDebug() << "Start AlbumModelTest::testTAlbumModel()"; TagModel* albumModel = new TagModel(); ModelTest* test = new ModelTest(albumModel, 0); delete test; delete albumModel; albumModel = new TagModel(AbstractAlbumModel::IgnoreRootAlbum); test = new ModelTest(albumModel, 0); delete test; delete albumModel; } void AlbumModelTest::testSAlbumModel() { qDebug() << "Start AlbumModelTest::testSAlbumModel()"; SearchModel* const albumModel = new SearchModel(); ModelTest* const test = new ModelTest(albumModel, 0); delete test; delete albumModel; } diff --git a/core/tests/database/databasetagstest.cpp b/core/tests/database/databasetagstest.cpp index 37e5d99991..0aeb0f40aa 100644 --- a/core/tests/database/databasetagstest.cpp +++ b/core/tests/database/databasetagstest.cpp @@ -1,805 +1,805 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2015-12-13 * Description : test cases for tags tree manipulation in database * * Copyright (C) 2015-2018 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 "databasetagstest.h" // Qt includes #include #include #include #include // Local includes #include "albummanager.h" #include "albummodel.h" #include "albumthumbnailloader.h" #include "collectionlocation.h" #include "collectionmanager.h" #include "loadingcacheinterface.h" #include "scancontroller.h" #include "thumbnailloadthread.h" using namespace Digikam; const QString IMAGE_PATH(QFINDTESTDATA("data/")); QTEST_MAIN(DatabaseTagsTest) DatabaseTagsTest::DatabaseTagsTest() : palbumRoot0(0), palbumRoot1(0), palbumRoot2(0), palbumChild0Root0(0), palbumChild1Root0(0), palbumChild2Root0(0), palbumChild0Root1(0), rootTag(0), talbumRoot0(0), talbumRoot1(0), talbumChild0Root0(0), talbumChild1Root0(0), talbumChild0Child1Root0(0), talbumChild0Root1(0) { } DatabaseTagsTest::~DatabaseTagsTest() { } void DatabaseTagsTest::initTestCase() { tempSuffix = QLatin1String("databasetagstest-") + QTime::currentTime().toString(); - dbPath = QDir::temp().absolutePath() + QLatin1String("/") + tempSuffix; + dbPath = QDir::temp().absolutePath() + QLatin1Char('/') + tempSuffix; if (QDir::temp().exists(tempSuffix)) { QString msg = QLatin1String("Error creating temp path") + dbPath; QVERIFY2(false, msg.toLatin1().constData()); } QDir::temp().mkdir(tempSuffix); qDebug() << "Using database path for test: " << dbPath; // use a testing database AlbumManager::instance(); AlbumManager::checkDatabaseDirsAfterFirstRun(QDir::temp().absoluteFilePath( tempSuffix), QDir::temp().absoluteFilePath(tempSuffix)); DbEngineParameters params = DbEngineParameters::defaultParameters(QLatin1String("QMYSQL")); params.setInternalServerPath(dbPath); DatabaseServerStarter::startServerManagerProcess(); bool dbChangeGood = AlbumManager::instance()->setDatabase(params, false, QDir::temp().absoluteFilePath(tempSuffix)); QVERIFY2(dbChangeGood, "Could not set temp album db"); QList locs = CollectionManager::instance()->allAvailableLocations(); QVERIFY2(locs.size(), "Failed to auto-create one collection in setDatabase"); ScanController::instance()->completeCollectionScan(); AlbumManager::instance()->startScan(); AlbumList all = AlbumManager::instance()->allPAlbums(); qDebug() << "PAlbum registered : " << all.size(); foreach(Album* const a, all) { if (a) { qDebug() << " ==> Id : " << a->id() << " , is root : " << a->isRoot() << " , title : " << a->title(); } } QVERIFY2( all.size() == 3, "Failed to scan empty directory. We must have one root album, one album, and one trash."); } void DatabaseTagsTest::cleanupTestCase() { qDebug() << "Start DatabaseTagsTest::cleanupTestCase()"; ScanController::instance()->shutDown(); AlbumManager::instance()->cleanUp(); ThumbnailLoadThread::cleanUp(); AlbumThumbnailLoader::instance()->cleanUp(); LoadingCacheInterface::cleanUp(); QDir dir(dbPath); dir.removeRecursively(); qDebug() << "deleted test folder " << dbPath; } // Qt test doesn't use exceptions, so using assertion macros in methods called // from a test slot doesn't stop the test method and may result in inconsistent // data or segfaults. Therefore use macros for these functions. #define safeCreatePAlbum(parent, name, result) \ { \ QString error; \ result = AlbumManager::instance()->createPAlbum(parent, name, name, \ QDate::currentDate(), albumCategory, error); \ QVERIFY2(result, QString::fromUtf8("Error creating PAlbum for test: %1").arg(error).toLatin1().constData()); \ } #define safeCreateTAlbum(parent, name, result) \ { \ QString error; \ result = AlbumManager::instance()->createTAlbum(parent, name, QLatin1String(""), error); \ QVERIFY2(result, QString::fromUtf8("Error creating TAlbum for test: %1").arg(error).toLatin1().constData()); \ } void DatabaseTagsTest::init() { qDebug() << "Start DatabaseTagsTest::init()"; /* palbumCountMap.clear(); // create a model to check that model work is done correctly while scanning addedIds.clear(); startModel = new AlbumModel; startModel->setShowCount(true); connect(startModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(slotStartModelRowsInserted(QModelIndex,int,int))); connect(startModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(slotStartModelDataChanged(QModelIndex,QModelIndex))); qDebug() << "Created startModel" << startModel; // ensure that this model is empty in the beginning except for the root // album and the collection that include trash QCOMPARE(startModel->rowCount(), 1); QModelIndex rootIndex = startModel->index(0, 0); QCOMPARE(startModel->rowCount(rootIndex), 1); QModelIndex collectionIndex = startModel->index(0, 0, rootIndex); QCOMPARE(startModel->rowCount(collectionIndex), 1); // insert some test data // physical albums // create two of them by creating directories and scanning QDir dir(dbPath); dir.mkdir(QLatin1String("root0")); dir.mkdir(QLatin1String("root1")); ScanController::instance()->completeCollectionScan(); AlbumManager::instance()->refresh(); QCOMPARE(AlbumManager::instance()->allPAlbums().size(), 5); QString error; palbumRoot0 = AlbumManager::instance()->findPAlbum(QUrl::fromLocalFile(dbPath + QLatin1String("/root0"))); QVERIFY2(palbumRoot0, "Error having PAlbum root0 in AlbumManager"); palbumRoot1 = AlbumManager::instance()->findPAlbum(QUrl::fromLocalFile(dbPath + QLatin1String("/root1"))); QVERIFY2(palbumRoot1, "Error having PAlbum root1 in AlbumManager"); // Create some more through AlbumManager palbumRoot2 = AlbumManager::instance()->createPAlbum(dbPath, QLatin1String("root2"), QLatin1String("root album 2"), QDate::currentDate(), albumCategory, error); QVERIFY2(palbumRoot2, QString::fromUtf8("Error creating PAlbum for test: %1").arg(error).toLatin1().constData()); safeCreatePAlbum(palbumRoot0, QLatin1String("root0child0"), palbumChild0Root0); safeCreatePAlbum(palbumRoot0, QLatin1String("root0child1"), palbumChild1Root0); const QString sameName = QLatin1String("sameName Album"); safeCreatePAlbum(palbumRoot0, sameName, palbumChild2Root0); safeCreatePAlbum(palbumRoot1, sameName, palbumChild0Root1); qDebug() << "AlbumManager now knows these PAlbums:"; foreach(Album* const a, AlbumManager::instance()->allPAlbums()) { qDebug() << "\t" << a->title(); } // tags rootTag = AlbumManager::instance()->findTAlbum(0); QVERIFY(rootTag); safeCreateTAlbum(rootTag, QLatin1String("root0"), talbumRoot0); safeCreateTAlbum(rootTag, QLatin1String("root1"), talbumRoot1); safeCreateTAlbum(talbumRoot0, QLatin1String("child0 root 0"), talbumChild0Root0); safeCreateTAlbum(talbumRoot0, QLatin1String("child1 root 0"), talbumChild1Root0); safeCreateTAlbum(talbumChild1Root0, sameName, talbumChild0Child1Root0); safeCreateTAlbum(talbumRoot1, sameName, talbumChild0Root1); qDebug() << "created tags"; // add some images for having date albums QDir imageDir(IMAGE_PATH); imageDir.setNameFilters(QStringList() << QLatin1String("*.jpg")); QStringList imageFiles = imageDir.entryList(); qDebug() << "copying images " << imageFiles << " to " << palbumChild0Root0->fileUrl(); foreach(const QString& imageFile, imageFiles) { QString src = IMAGE_PATH + QLatin1Char('/') + imageFile; QString dst = palbumChild0Root0->fileUrl().toLocalFile() + QLatin1Char('/') + imageFile; bool copied = QFile::copy(src, dst); QVERIFY2(copied, "Test images must be copied"); } ScanController::instance()->completeCollectionScan(); if (AlbumManager::instance()->allDAlbums().count() <= 1) { ensureItemCounts(); } qDebug() << "date albums: " << AlbumManager::instance()->allDAlbums(); // root + 2 years + 2 and 3 months per year + (1997 as test year for date ordering with 12 months) = 21 QCOMPARE(AlbumManager::instance()->allDAlbums().size(), 21); // ensure that there is a root date album DAlbum* const rootFromAlbumManager = AlbumManager::instance()->findDAlbum(0); QVERIFY(rootFromAlbumManager); DAlbum* rootFromList = 0; foreach(Album* const album, AlbumManager::instance()->allDAlbums()) { DAlbum* const dAlbum = dynamic_cast (album); QVERIFY(dAlbum); if (dAlbum->isRoot()) { rootFromList = dAlbum; } } QVERIFY(rootFromList); QVERIFY(rootFromList == rootFromAlbumManager); */ } void DatabaseTagsTest::cleanup() { /* if (startModel) { disconnect(startModel); } delete startModel; addedIds.clear(); // remove all test data AlbumManager::instance()->refresh(); // remove all palbums' directories deletePAlbum(palbumRoot0); deletePAlbum(palbumRoot1); deletePAlbum(palbumRoot2); // take over changes to database ScanController::instance()->completeCollectionScan(); // reread from database AlbumManager::instance()->refresh(); // root + one collection QCOMPARE(AlbumManager::instance()->allPAlbums().size(), 2); // remove all tags QString error; bool removed = AlbumManager::instance()->deleteTAlbum(talbumRoot0, error); QVERIFY2(removed, QString::fromUtf8("Error removing a tag: %1").arg(error).toLatin1().constData()); removed = AlbumManager::instance()->deleteTAlbum(talbumRoot1, error); QVERIFY2(removed, QString::fromUtf8("Error removing a tag: %1").arg(error).toLatin1().constData()); QCOMPARE(AlbumManager::instance()->allTAlbums().size(), 1); */ } /* void DatabaseTagsTest::testStartAlbumModel() { qDebug() << "Start DatabaseTagsTest::testStartAlbumModel()"; // verify that the start album model got all these changes // one root QCOMPARE(startModel->rowCount(), 1); // one collection QModelIndex rootIndex = startModel->index(0, 0); QCOMPARE(startModel->rowCount(rootIndex), 1); // two albums in the collection QModelIndex collectionIndex = startModel->index(0, 0, rootIndex); QCOMPARE(startModel->rowCount(collectionIndex), 3); // this is should be enough for now // We must have received an added notation for everything except album root // and collection QCOMPARE(addedIds.size(), 7); } void DatabaseTagsTest::ensureItemCounts() { // trigger listing job QEventLoop dAlbumLoop; connect(AlbumManager::instance(), SIGNAL(signalAllDAlbumsLoaded()), &dAlbumLoop, SLOT(quit())); AlbumManager::instance()->prepareItemCounts(); qDebug() << "Waiting for AlbumManager and the IOSlave to create DAlbums..."; dAlbumLoop.exec(); qDebug() << "DAlbums were created"; while (palbumCountMap.size() < 8) { QEventLoop pAlbumLoop; connect(AlbumManager::instance(), SIGNAL(signalPAlbumsDirty(QMap)), &pAlbumLoop, SLOT(quit())); qDebug() << "Waiting for first PAlbum count map"; pAlbumLoop.exec(); qDebug() << "Got new PAlbum count map"; } } void DatabaseTagsTest::slotStartModelRowsInserted(const QModelIndex& parent, int start, int end) { qDebug() << "called, parent:" << parent << ", start:" << start << ", end:" << end; for (int row = start; row <= end; ++row) { QModelIndex child = startModel->index(row, 0, parent); QVERIFY(child.isValid()); Album* album = startModel->albumForIndex(child); const int id = child.data(AbstractAlbumModel::AlbumIdRole).toInt(); QVERIFY(album); qDebug() << "added album with id" << id << "and name" << album->title(); addedIds << id; } } void DatabaseTagsTest::slotStartModelDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { for (int row = topLeft.row(); row <= bottomRight.row(); ++row) { QModelIndex index = startModel->index(row, topLeft.column(), topLeft.parent()); if (!index.isValid()) { qDebug() << "Illegal index received"; continue; } int albumId = index.data(AbstractAlbumModel::AlbumIdRole).toInt(); if (!addedIds.contains(albumId)) { QString message = QLatin1String("Album id ") + QString::number(albumId) + QLatin1String(" was changed before adding signal was received"); QFAIL(message.toLatin1().constData()); qDebug() << message; } } } void DatabaseTagsTest::deletePAlbum(PAlbum* album) { QDir dir(album->folderPath()); dir.removeRecursively(); } void DatabaseTagsTest::setLastPAlbumCountMap(const QMap &map) { qDebug() << "Receiving new count map "<< map; palbumCountMap = map; } void DatabaseTagsTest::testPAlbumModel() { qDebug() << "Start DatabaseTagsTest::testPAlbumModel()"; AlbumModel* albumModel = new AlbumModel(); ModelTest* test = new ModelTest(albumModel, 0); delete test; delete albumModel; albumModel = new AlbumModel(AbstractAlbumModel::IgnoreRootAlbum); test = new ModelTest(albumModel, 0); delete test; delete albumModel; } void DatabaseTagsTest::testDisablePAlbumCount() { qDebug() << "Start DatabaseTagsTest::testDisablePAlbumCount()"; AlbumModel albumModel; albumModel.setCountMap(palbumCountMap); albumModel.setShowCount(true); QRegExp countRegEx(QLatin1String(".+ \\(\\d+\\)")); countRegEx.setMinimal(true); QVERIFY(countRegEx.exactMatch(QLatin1String("test (10)"))); QVERIFY(countRegEx.exactMatch(QLatin1String("te st (10)"))); QVERIFY(countRegEx.exactMatch(QLatin1String("te st (0)"))); QVERIFY(!countRegEx.exactMatch(QLatin1String("te st ()"))); QVERIFY(!countRegEx.exactMatch(QLatin1String("te st"))); QVERIFY(!countRegEx.exactMatch(QLatin1String("te st (10) bla"))); // ensure that all albums except the root album have a count attached QModelIndex rootIndex = albumModel.index(0, 0, QModelIndex()); QString rootTitle = albumModel.data(rootIndex, Qt::DisplayRole).toString(); QVERIFY(!countRegEx.exactMatch(rootTitle)); for (int collectionRow = 0; collectionRow < albumModel.rowCount(rootIndex); ++collectionRow) { QModelIndex collectionIndex = albumModel.index(collectionRow, 0, rootIndex); QString collectionTitle = albumModel.data(collectionIndex, Qt::DisplayRole).toString(); QVERIFY2(countRegEx.exactMatch(collectionTitle), QString::fromUtf8("%1 matching error").arg(collectionTitle).toLatin1().constData()); for (int albumRow = 0; albumRow < albumModel.rowCount(collectionIndex); ++albumRow) { QModelIndex albumIndex = albumModel.index(albumRow, 0, collectionIndex); QString albumTitle = albumModel.data(albumIndex, Qt::DisplayRole).toString(); QVERIFY2(countRegEx.exactMatch(albumTitle), QString::fromUtf8("%1 matching error").arg(albumTitle).toLatin1().constData()); } } // now disable showing the count albumModel.setShowCount(false); // ensure that no album has a count attached rootTitle = albumModel.data(rootIndex, Qt::DisplayRole).toString(); QVERIFY(!countRegEx.exactMatch(rootTitle)); for (int collectionRow = 0; collectionRow < albumModel.rowCount(rootIndex); ++collectionRow) { QModelIndex collectionIndex = albumModel.index(collectionRow, 0, rootIndex); QString collectionTitle = albumModel.data(collectionIndex, Qt::DisplayRole).toString(); QVERIFY2(!countRegEx.exactMatch(collectionTitle), QString::fromUtf8("%1 matching error").arg(collectionTitle).toLatin1().constData()); for (int albumRow = 0; albumRow < albumModel.rowCount(collectionIndex); ++albumRow) { QModelIndex albumIndex = albumModel.index(albumRow, 0, collectionIndex); QString albumTitle = albumModel.data(albumIndex, Qt::DisplayRole).toString(); QVERIFY2(!countRegEx.exactMatch(albumTitle), QString::fromUtf8("%1 matching error").arg(albumTitle).toLatin1().constData()); } } } void DatabaseTagsTest::testDAlbumModel() { qDebug() << "Start DatabaseTagsTest::testDAlbumModel()"; DateAlbumModel* const albumModel = new DateAlbumModel(); ensureItemCounts(); ModelTest* const test = new ModelTest(albumModel, 0); delete test; delete albumModel; } void DatabaseTagsTest::testDAlbumContainsAlbums() { qDebug() << "Start DatabaseTagsTest::testDAlbumContainsAlbums()"; DateAlbumModel* const albumModel = new DateAlbumModel(); ensureItemCounts(); QVERIFY(albumModel->rootAlbum()); foreach(Album* const album, AlbumManager::instance()->allDAlbums()) { DAlbum* const dAlbum = dynamic_cast (album); QVERIFY(dAlbum); qDebug() << "checking album for date " << dAlbum->date() << ", range = " << dAlbum->range(); QModelIndex index = albumModel->indexForAlbum(dAlbum); if (!dAlbum->isRoot()) { QVERIFY(index.isValid()); } if (dAlbum->isRoot()) { // root album QVERIFY(dAlbum->isRoot()); QCOMPARE(albumModel->rowCount(index), 3); QCOMPARE(index, albumModel->rootAlbumIndex()); } else if (dAlbum->range() == DAlbum::Year && dAlbum->date().year() == 2007) { QCOMPARE(albumModel->rowCount(index), 2); } else if (dAlbum->range() == DAlbum::Year && dAlbum->date().year() == 2009) { QCOMPARE(albumModel->rowCount(index), 3); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2007 && dAlbum->date().month() == 3) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2007 && dAlbum->date().month() == 4) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2009 && dAlbum->date().month() == 3) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2009 && dAlbum->date().month() == 4) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->range() == DAlbum::Month && dAlbum->date().year() == 2009 && dAlbum->date().month() == 5) { QCOMPARE(albumModel->rowCount(index), 0); } else if (dAlbum->date().year() == 1997) { // Ignore these albums for order testing } else { qDebug() << "Unexpected album: " << dAlbum->title(); QFAIL("Unexpected album returned from model"); } } delete albumModel; } void DatabaseTagsTest::testDAlbumSorting() { qDebug() << "Start DatabaseTagsTest::testDAlbumSorting()"; DateAlbumModel dateAlbumModel; AlbumFilterModel albumModel; albumModel.setSourceAlbumModel(&dateAlbumModel); // first check ascending order albumModel.sort(0, Qt::AscendingOrder); int previousYear = 0; for (int yearRow = 0; yearRow < albumModel.rowCount(); ++yearRow) { QModelIndex yearIndex = albumModel.index(yearRow, 0); DAlbum* const yearAlbum = dynamic_cast (albumModel.albumForIndex(yearIndex)); QVERIFY(yearAlbum); QVERIFY(yearAlbum->date().year() > previousYear); previousYear = yearAlbum->date().year(); int previousMonth = 0; for (int monthRow = 0; monthRow < albumModel.rowCount(yearIndex); ++monthRow) { QModelIndex monthIndex = albumModel.index(monthRow, 0, yearIndex); DAlbum* const monthAlbum = dynamic_cast (albumModel.albumForIndex(monthIndex)); QVERIFY(monthAlbum); QVERIFY(monthAlbum->date().month() > previousMonth); previousMonth = monthAlbum->date().month(); } } // then check descending order albumModel.sort(0, Qt::DescendingOrder); previousYear = 1000000; for (int yearRow = 0; yearRow < albumModel.rowCount(); ++yearRow) { QModelIndex yearIndex = albumModel.index(yearRow, 0); DAlbum* const yearAlbum = dynamic_cast (albumModel.albumForIndex(yearIndex)); QVERIFY(yearAlbum); QVERIFY(yearAlbum->date().year() < previousYear); previousYear = yearAlbum->date().year(); int previousMonth = 13; for (int monthRow = 0; monthRow < albumModel.rowCount(yearIndex); ++monthRow) { QModelIndex monthIndex = albumModel.index(monthRow, 0, yearIndex); DAlbum* const monthAlbum = dynamic_cast (albumModel.albumForIndex(monthIndex)); QVERIFY(monthAlbum); QVERIFY(monthAlbum->date().month() < previousMonth); previousMonth = monthAlbum->date().month(); } } } void DatabaseTagsTest::testDAlbumCount() { qDebug() << "Start DatabaseTagsTest::testDAlbumCount()"; DateAlbumModel* const albumModel = new DateAlbumModel(); albumModel->setShowCount(true); ensureItemCounts(); qDebug() << "iterating over root indices"; // check year albums for (int yearRow = 0; yearRow < albumModel->rowCount(albumModel->rootAlbumIndex()); ++yearRow) { QModelIndex yearIndex = albumModel->index(yearRow, 0); DAlbum* const yearDAlbum = albumModel->albumForIndex(yearIndex); QVERIFY(yearDAlbum); QVERIFY(yearDAlbum->range() == DAlbum::Year); if (yearDAlbum->date().year() == 2007) { const int imagesInYear = 7; albumModel->includeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), imagesInYear); albumModel->excludeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), 0); albumModel->includeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), imagesInYear); for (int monthRow = 0; monthRow < albumModel->rowCount(yearIndex); ++monthRow) { QModelIndex monthIndex = albumModel->index(monthRow, 0, yearIndex); DAlbum* monthDAlbum = albumModel->albumForIndex(monthIndex); QVERIFY(monthDAlbum); QVERIFY(monthDAlbum->range() == DAlbum::Month); QVERIFY(monthDAlbum->date().year() == 2007); if (monthDAlbum->date().month() == 3) { const int imagesInMonth = 3; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else if (monthDAlbum->date().month() == 4) { const int imagesInMonth = 4; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else { QFAIL("unexpected month album in 2007"); } } } else if (yearDAlbum->date().year() == 2009) { const int imagesInYear = 5; albumModel->includeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), imagesInYear); albumModel->excludeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), 0); albumModel->includeChildrenCount(yearIndex); QCOMPARE(albumModel->albumCount(yearDAlbum), imagesInYear); for (int monthRow = 0; monthRow < albumModel->rowCount(yearIndex); ++monthRow) { QModelIndex monthIndex = albumModel->index(monthRow, 0, yearIndex); DAlbum* monthDAlbum = albumModel->albumForIndex(monthIndex); QVERIFY(monthDAlbum); QVERIFY(monthDAlbum->range() == DAlbum::Month); QVERIFY(monthDAlbum->date().year() == 2009); if (monthDAlbum->date().month() == 3) { const int imagesInMonth = 2; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else if (monthDAlbum->date().month() == 4) { const int imagesInMonth = 2; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else if (monthDAlbum->date().month() == 5) { const int imagesInMonth = 1; albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->excludeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); albumModel->includeChildrenCount(monthIndex); QCOMPARE(albumModel->albumCount(monthDAlbum), imagesInMonth); } else { QFAIL("unexpected month album in 2009"); } } } else if (yearDAlbum->date().year() == 1997) { // Nothing to do here, ignore the albums for ordering tests } else { QFAIL("Received unexpected album from model"); } } delete albumModel; } void DatabaseTagsTest::testTAlbumModel() { qDebug() << "Start DatabaseTagsTest::testTAlbumModel()"; TagModel* albumModel = new TagModel(); ModelTest* test = new ModelTest(albumModel, 0); delete test; delete albumModel; albumModel = new TagModel(AbstractAlbumModel::IgnoreRootAlbum); test = new ModelTest(albumModel, 0); delete test; delete albumModel; } void DatabaseTagsTest::testSAlbumModel() { qDebug() << "Start DatabaseTagsTest::testSAlbumModel()"; SearchModel* const albumModel = new SearchModel(); ModelTest* const test = new ModelTest(albumModel, 0); delete test; delete albumModel; } */ diff --git a/core/utilities/assistants/panorama/tasks/panotask.cpp b/core/utilities/assistants/panorama/tasks/panotask.cpp index 7009882594..780090cc9b 100644 --- a/core/utilities/assistants/panorama/tasks/panotask.cpp +++ b/core/utilities/assistants/panorama/tasks/panotask.cpp @@ -1,54 +1,54 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2012-03-15 * Description : a tool to create panorama by fusion of several images. * * Copyright (C) 2012-2016 by Benjamin Girault * * 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 "panotask.h" // Qt includes #include namespace Digikam { PanoTask::PanoTask(PanoAction action, const QString& workDirPath) : action(action), isAbortedFlag(false), successFlag(false), - tmpDir(QUrl::fromLocalFile(workDirPath + QLatin1String("/"))) + tmpDir(QUrl::fromLocalFile(workDirPath + QLatin1Char('/'))) { } PanoTask::~PanoTask() { } bool PanoTask::success() const { return successFlag; } void PanoTask::requestAbort() { isAbortedFlag = true; } } // namespace Digikam diff --git a/core/utilities/assistants/printcreator/manager/advprinttask.cpp b/core/utilities/assistants/printcreator/manager/advprinttask.cpp index 552bd704fc..0d9024df32 100644 --- a/core/utilities/assistants/printcreator/manager/advprinttask.cpp +++ b/core/utilities/assistants/printcreator/manager/advprinttask.cpp @@ -1,705 +1,705 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2007-11-07 * Description : a tool to print images * * Copyright (C) 2017-2018 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 "advprinttask.h" // C++ includes #include // Qt includes #include #include #include #include // KDE includes #include // Local includes #include "advprintwizard.h" #include "advprintphoto.h" #include "advprintcaptionpage.h" #include "dmetadata.h" #include "dfileoperations.h" #include "dimg.h" #include "digikam_debug.h" #include "digikam_config.h" namespace Digikam { class AdvPrintTask::Private { public: explicit Private() : settings(0), mode(AdvPrintTask::PRINT), sizeIndex(0) { } public: AdvPrintSettings* settings; PrintMode mode; QSize size; int sizeIndex; }; // ------------------------------------------------------- AdvPrintTask::AdvPrintTask(AdvPrintSettings* const settings, PrintMode mode, const QSize& size, int sizeIndex) : ActionJob(), d(new Private) { d->settings = settings; d->mode = mode; d->size = size; d->sizeIndex = sizeIndex; } AdvPrintTask::~AdvPrintTask() { cancel(); delete d; } void AdvPrintTask::run() { switch (d->mode) { case PREPAREPRINT: qCDebug(DIGIKAM_GENERAL_LOG) << "Start prepare to print"; preparePrint(); emit signalDone(!m_cancel); qCDebug(DIGIKAM_GENERAL_LOG) << "Prepare to print is done"; break; case PRINT: qCDebug(DIGIKAM_GENERAL_LOG) << "Start to print"; if (d->settings->printerName != d->settings->outputName(AdvPrintSettings::FILES) && d->settings->printerName != d->settings->outputName(AdvPrintSettings::GIMP)) { printPhotos(); emit signalDone(!m_cancel); } else { QStringList files = printPhotosToFile(); if (d->settings->printerName == d->settings->outputName(AdvPrintSettings::GIMP)) { d->settings->gimpFiles << files; } emit signalDone(!m_cancel && !files.isEmpty()); } qCDebug(DIGIKAM_GENERAL_LOG) << "Print is done"; break; default: // PREVIEW qCDebug(DIGIKAM_GENERAL_LOG) << "Start to compute preview"; QImage img(d->size, QImage::Format_ARGB32_Premultiplied); QPainter p(&img); p.setCompositionMode(QPainter::CompositionMode_Clear); p.fillRect(img.rect(), Qt::color0); p.setCompositionMode(QPainter::CompositionMode_SourceOver); paintOnePage(p, d->settings->photos, d->settings->outputLayouts->m_layouts, d->settings->currentPreviewPage, d->settings->disableCrop, true); p.end(); if (!m_cancel) emit signalPreview(img); qCDebug(DIGIKAM_GENERAL_LOG) << "Preview computation is done"; break; } } void AdvPrintTask::preparePrint() { int photoIndex = 0; for (QList::iterator it = d->settings->photos.begin() ; it != d->settings->photos.end() ; ++it) { AdvPrintPhoto* const photo = static_cast(*it); if (photo && photo->m_cropRegion == QRect(-1, -1, -1, -1)) { QRect* const curr = d->settings->getLayout(photoIndex, d->sizeIndex); photo->updateCropRegion(curr->width(), curr->height(), d->settings->outputLayouts->m_autoRotate); } photoIndex++; emit signalProgress(photoIndex); if (m_cancel) { signalMessage(i18n("Printing canceled"), true); return; } } } void AdvPrintTask::printPhotos() { AdvPrintPhotoSize* const layouts = d->settings->outputLayouts; QPrinter* const printer = d->settings->outputPrinter; Q_ASSERT(layouts); Q_ASSERT(printer); Q_ASSERT(layouts->m_layouts.count() > 1); QList photos = d->settings->photos; QPainter p; p.begin(printer); int current = 0; int pageCount = 1; bool printing = true; while (printing) { signalMessage(i18n("Processing page %1", pageCount), false); printing = paintOnePage(p, photos, layouts->m_layouts, current, d->settings->disableCrop); if (printing) { printer->newPage(); } pageCount++; emit signalProgress(current); if (m_cancel) { printer->abort(); signalMessage(i18n("Printing canceled"), true); return; } } p.end(); } QStringList AdvPrintTask::printPhotosToFile() { AdvPrintPhotoSize* const layouts = d->settings->outputLayouts; QString dir = d->settings->outputPath; Q_ASSERT(layouts); Q_ASSERT(!dir.isEmpty()); Q_ASSERT(layouts->m_layouts.count() > 1); QList photos = d->settings->photos; QStringList files; int current = 0; int pageCount = 1; bool printing = true; QRect* const srcPage = layouts->m_layouts.at(0); while (printing) { // make a pixmap to save to file. Make it just big enough to show the // highest-dpi image on the page without losing data. double dpi = layouts->m_dpi; if (dpi == 0.0) { dpi = getMaxDPI(photos, layouts->m_layouts, current) * 1.1; } int w = AdvPrintWizard::normalizedInt(srcPage->width()); int h = AdvPrintWizard::normalizedInt(srcPage->height()); QImage image(w, h, QImage::Format_ARGB32_Premultiplied); QPainter painter; painter.begin(&image); QString ext = d->settings->format(); QString name = QLatin1String("output"); - QString filename = dir + QLatin1String("/") + - name + QLatin1String("_") + + QString filename = dir + QLatin1Char('/') + + name + QLatin1Char('_') + QString::number(pageCount) + - QLatin1String(".") + ext; + QLatin1Char('.') + ext; if (QFile::exists(filename) && d->settings->conflictRule != FileSaveConflictBox::OVERWRITE) { filename = DFileOperations::getUniqueFileUrl(QUrl::fromLocalFile(filename)).toLocalFile(); } signalMessage(i18n("Processing page %1", pageCount), false); printing = paintOnePage(painter, photos, layouts->m_layouts, current, d->settings->disableCrop); painter.end(); if (!image.save(filename, 0, 100)) { signalMessage(i18n("Could not save file %1", filename), true); break; } else { files.append(filename); signalMessage(i18n("Page %1 saved as %2", pageCount, filename), false); } pageCount++; emit signalProgress(current); if (m_cancel) { signalMessage(i18n("Printing canceled"), true); break; } } return files; } bool AdvPrintTask::paintOnePage(QPainter& p, const QList& photos, const QList& layouts, int& current, bool cropDisabled, bool useThumbnails) { if (layouts.isEmpty()) { qCWarning(DIGIKAM_GENERAL_LOG) << "Invalid layout content"; return true; } if (photos.count() == 0) { qCWarning(DIGIKAM_GENERAL_LOG) << "no photo to print"; // no photos => last photo return true; } QList::const_iterator it = layouts.begin(); QRect* const srcPage = static_cast(*it); ++it; QRect* layout = static_cast(*it); // scale the page size to best fit the painter // size the rectangle based on the minimum image dimension int destW = p.window().width(); int destH = p.window().height(); int srcW = srcPage->width(); int srcH = srcPage->height(); if (destW < destH) { destH = AdvPrintWizard::normalizedInt((double) destW * ((double) srcH / (double) srcW)); if (destH > p.window().height()) { destH = p.window().height(); destW = AdvPrintWizard::normalizedInt((double) destH * ((double) srcW / (double) srcH)); } } else { destW = AdvPrintWizard::normalizedInt((double) destH * ((double) srcW / (double) srcH)); if (destW > p.window().width()) { destW = p.window().width(); destH = AdvPrintWizard::normalizedInt((double) destW * ((double) srcH / (double) srcW)); } } double xRatio = (double) destW / (double) srcPage->width(); double yRatio = (double) destH / (double) srcPage->height(); int left = (p.window().width() - destW) / 2; int top = (p.window().height() - destH) / 2; // FIXME: may not want to erase the background page p.eraseRect(left, top, AdvPrintWizard::normalizedInt((double) srcPage->width() * xRatio), AdvPrintWizard::normalizedInt((double) srcPage->height() * yRatio)); for (; (current < photos.count()) && !m_cancel ; ++current) { AdvPrintPhoto* const photo = photos.at(current); // crop QImage img; if (useThumbnails) { img = photo->thumbnail().copyQImage(); } else { img = photo->loadPhoto().copyQImage(); } // next, do we rotate? if (photo->m_rotation != 0) { // rotate QMatrix matrix; matrix.rotate(photo->m_rotation); img = img.transformed(matrix); } if (useThumbnails) { // scale the crop region to thumbnail coords double xRatio = 0.0; double yRatio = 0.0; if (photo->thumbnail().width() != 0) { xRatio = (double)photo->thumbnail().width() / (double)photo->width(); } if (photo->thumbnail().height() != 0) { yRatio = (double)photo->thumbnail().height() / (double)photo->height(); } int x1 = AdvPrintWizard::normalizedInt((double)photo->m_cropRegion.left() * xRatio); int y1 = AdvPrintWizard::normalizedInt((double)photo->m_cropRegion.top() * yRatio); int w = AdvPrintWizard::normalizedInt((double)photo->m_cropRegion.width() * xRatio); int h = AdvPrintWizard::normalizedInt((double)photo->m_cropRegion.height() * yRatio); img = img.copy(QRect(x1, y1, w, h)); } else if (!cropDisabled) { img = img.copy(photo->m_cropRegion); } int x1 = AdvPrintWizard::normalizedInt((double) layout->left() * xRatio); int y1 = AdvPrintWizard::normalizedInt((double) layout->top() * yRatio); int w = AdvPrintWizard::normalizedInt((double) layout->width() * xRatio); int h = AdvPrintWizard::normalizedInt((double) layout->height() * yRatio); QRect rectViewPort = p.viewport(); QRect newRectViewPort = QRect(x1 + left, y1 + top, w, h); QSize imageSize = img.size(); /* qCDebug(DIGIKAM_GENERAL_LOG) << "Image " << photo->filename << " size " << imageSize; qCDebug(DIGIKAM_GENERAL_LOG) << "viewport size " << newRectViewPort.size(); */ QPoint point; if (cropDisabled) { imageSize.scale(newRectViewPort.size(), Qt::KeepAspectRatio); int spaceLeft = (newRectViewPort.width() - imageSize.width()) / 2; int spaceTop = (newRectViewPort.height() - imageSize.height()) / 2; p.setViewport(spaceLeft + newRectViewPort.x(), spaceTop + newRectViewPort.y(), imageSize.width(), imageSize.height()); point = QPoint(newRectViewPort.x() + spaceLeft + imageSize.width(), newRectViewPort.y() + spaceTop + imageSize.height()); } else { p.setViewport(newRectViewPort); point = QPoint(x1 + left + w, y1 + top + w); } QRect rectWindow = p.window(); p.setWindow(img.rect()); p.drawImage(0, 0, img); p.setViewport(rectViewPort); p.setWindow(rectWindow); p.setBrushOrigin(point); if (photo->m_pAdvPrintCaptionInfo && photo->m_pAdvPrintCaptionInfo->m_captionType != AdvPrintSettings::NONE) { p.save(); QString caption = AdvPrintCaptionPage::captionFormatter(photo); qCDebug(DIGIKAM_GENERAL_LOG) << "Caption for" << photo->m_url << ":" << caption; // draw the text at (0,0), but we will translate and rotate the world // before drawing so the text will be in the correct location // next, do we rotate? int captionW = w - 2; double ratio = photo->m_pAdvPrintCaptionInfo->m_captionSize * 0.01; int captionH = (int)(qMin(w, h) * ratio); int exifOrientation = DMetadata::ORIENTATION_NORMAL; int orientatation = photo->m_rotation; if (photo->m_iface) { DItemInfo info(photo->m_iface->itemInfo(photo->m_url)); exifOrientation = info.orientation(); } else { DMetadata meta(photo->m_url.toLocalFile()); exifOrientation = meta.getImageOrientation(); } // ROT_90_HFLIP .. ROT_270 if (exifOrientation == DMetadata::ORIENTATION_ROT_90_HFLIP || exifOrientation == DMetadata::ORIENTATION_ROT_90 || exifOrientation == DMetadata::ORIENTATION_ROT_90_VFLIP || exifOrientation == DMetadata::ORIENTATION_ROT_270) { orientatation = (photo->m_rotation + 270) % 360; // -90 degrees } if (orientatation == 90 || orientatation == 270) { captionW = h; } p.rotate(orientatation); qCDebug(DIGIKAM_GENERAL_LOG) << "rotation " << photo->m_rotation << " orientation " << orientatation; int tx = left; int ty = top; switch (orientatation) { case 0 : { tx += x1 + 1; ty += y1 + (h - captionH - 1); break; } case 90 : { tx = top + y1 + 1; ty = -left - x1 - captionH - 1; break; } case 180 : { tx = -left - x1 - w + 1; ty = -top - y1 - (captionH + 1); break; } case 270 : { tx = -top - y1 - h + 1; ty = left + x1 + (w - captionH) - 1; break; } } p.translate(tx, ty); printCaption(p, photo, captionW, captionH, caption); p.restore(); } // iterate to the next position ++it; layout = (it == layouts.end()) ? 0 : static_cast(*it); if (layout == 0) { current++; break; } } // did we print the last photo? return (current < photos.count()); } double AdvPrintTask::getMaxDPI(const QList& photos, const QList& layouts, int current) { Q_ASSERT(layouts.count() > 1); QList::const_iterator it = layouts.begin(); QRect* layout = static_cast(*it); double maxDPI = 0.0; for (; current < photos.count(); ++current) { AdvPrintPhoto* const photo = photos.at(current); double dpi = ((double) photo->m_cropRegion.width() + (double) photo->m_cropRegion.height()) / (((double) layout->width() / 1000.0) + ((double) layout->height() / 1000.0)); if (dpi > maxDPI) maxDPI = dpi; // iterate to the next position ++it; layout = (it == layouts.end()) ? 0 : static_cast(*it); if (layout == 0) { break; } } return maxDPI; } void AdvPrintTask::printCaption(QPainter& p, AdvPrintPhoto* const photo, int captionW, int captionH, const QString& caption) { QStringList captionByLines; int captionIndex = 0; while (captionIndex < caption.length()) { QString newLine; bool breakLine = false; // End Of Line found int currIndex; // Caption QString current index // Check minimal lines dimension // TODO: fix length, maybe useless int captionLineLocalLength = 40; for (currIndex = captionIndex ; currIndex < caption.length() && !breakLine ; ++currIndex) { if (caption[currIndex] == QLatin1Char('\n') || caption[currIndex].isSpace()) { breakLine = true; } } if (captionLineLocalLength <= (currIndex - captionIndex)) { captionLineLocalLength = (currIndex - captionIndex); } breakLine = false; for (currIndex = captionIndex; (currIndex <= captionIndex + captionLineLocalLength) && (currIndex < caption.length()) && !breakLine; ++currIndex) { breakLine = (caption[currIndex] == QLatin1Char('\n')) ? true : false; if (breakLine) newLine.append(QLatin1Char(' ')); else newLine.append(caption[currIndex]); } captionIndex = currIndex; // The line is ended if (captionIndex != caption.length()) { while (!newLine.endsWith(QLatin1Char(' '))) { newLine.truncate(newLine.length() - 1); captionIndex--; } } captionByLines.prepend(newLine.trimmed()); } QFont font(photo->m_pAdvPrintCaptionInfo->m_captionFont); font.setStyleHint(QFont::SansSerif); font.setPixelSize((int)(captionH * 0.8F)); // Font height ratio font.setWeight(QFont::Normal); QFontMetrics fm(font); int pixelsHigh = fm.height(); p.setFont(font); p.setPen(photo->m_pAdvPrintCaptionInfo->m_captionColor); qCDebug(DIGIKAM_GENERAL_LOG) << "Number of lines " << (int) captionByLines.count() ; // Now draw the caption // TODO allow printing captions per photo and on top, bottom and vertically for (int lineNumber = 0 ; lineNumber < (int) captionByLines.count() ; ++lineNumber) { if (lineNumber > 0) { p.translate(0, - (int)(pixelsHigh)); } QRect r(0, 0, captionW, captionH); p.drawText(r, Qt::AlignLeft, captionByLines[lineNumber], &r); } } } // namespace Digikam diff --git a/core/utilities/assistants/printcreator/wizard/advprintphotopage.cpp b/core/utilities/assistants/printcreator/wizard/advprintphotopage.cpp index 0d310b0994..4041ef1b22 100644 --- a/core/utilities/assistants/printcreator/wizard/advprintphotopage.cpp +++ b/core/utilities/assistants/printcreator/wizard/advprintphotopage.cpp @@ -1,1516 +1,1516 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2017-05-25 * Description : a tool to print images * * Copyright (C) 2017-2018 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 "advprintphotopage.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include // Local includes #include "digikam_debug.h" #include "advprintwizard.h" #include "advprintcustomdlg.h" #include "templateicon.h" namespace Digikam { static const char* const CUSTOM_PAGE_LAYOUT_NAME = I18N_NOOP("Custom"); class AdvPrintPhotoPage::Private { public: template class WizardUI : public QWidget, public Ui_Class { public: explicit WizardUI(QWidget* const parent) : QWidget(parent) { this->setupUi(this); } }; typedef WizardUI PhotoUI; public: explicit Private(QWizard* const dialog) : pageSetupDlg(0), printer(0), wizard(0), settings(0), iface(0) { photoUi = new PhotoUI(dialog); wizard = dynamic_cast(dialog); if (wizard) { settings = wizard->settings(); iface = wizard->iface(); } } PhotoUI* photoUi; QPageSetupDialog* pageSetupDlg; QPrinter* printer; QList printerList; AdvPrintWizard* wizard; AdvPrintSettings* settings; DInfoInterface* iface; }; AdvPrintPhotoPage::AdvPrintPhotoPage(QWizard* const wizard, const QString& title) : DWizardPage(wizard, title), d(new Private(wizard)) { d->photoUi->BtnPreviewPageUp->setIcon(QIcon::fromTheme(QLatin1String("go-next")) .pixmap(16, 16)); d->photoUi->BtnPreviewPageDown->setIcon(QIcon::fromTheme(QLatin1String("go-previous")) .pixmap(16, 16)); // ---------------------- d->photoUi->m_printer_choice->setEditable(false); d->photoUi->m_printer_choice->setWhatsThis(i18n("Select your preferred print output.")); // Populate hardcoded printers QMap map2 = AdvPrintSettings::outputNames(); QMap::const_iterator it2 = map2.constBegin(); while (it2 != map2.constEnd()) { d->photoUi->m_printer_choice->addSqueezedItem(it2.value(), (int)it2.key()); ++it2; } // Populate real printers d->printerList = QPrinterInfo::availablePrinters(); for (QList::iterator it = d->printerList.begin() ; it != d->printerList.end() ; ++it) { d->photoUi->m_printer_choice->addSqueezedItem(it->printerName()); } connect(d->photoUi->m_printer_choice, SIGNAL(activated(QString)), this, SLOT(slotOutputChanged(QString))); connect(d->photoUi->BtnPreviewPageUp, SIGNAL(clicked()), this, SLOT(slotBtnPreviewPageUpClicked())); connect(d->photoUi->BtnPreviewPageDown, SIGNAL(clicked()), this, SLOT(slotBtnPreviewPageDownClicked())); connect(d->photoUi->ListPhotoSizes, SIGNAL(currentRowChanged(int)), this, SLOT(slotListPhotoSizesSelected())); connect(d->photoUi->m_pagesetup, SIGNAL(clicked()), this, SLOT(slotPageSetup())); if (d->photoUi->mPrintList->layout()) { delete d->photoUi->mPrintList->layout(); } // ----------------------------------- d->photoUi->mPrintList->setIface(d->iface); d->photoUi->mPrintList->setAllowDuplicate(true); d->photoUi->mPrintList->setControlButtons(DImagesList::Add | DImagesList::Remove | DImagesList::MoveUp | DImagesList::MoveDown | DImagesList::Clear | DImagesList::Save | DImagesList::Load); d->photoUi->mPrintList->setControlButtonsPlacement(DImagesList::ControlButtonsAbove); d->photoUi->mPrintList->enableDragAndDrop(false); d->photoUi->BmpFirstPagePreview->setAlignment(Qt::AlignHCenter); connect(d->photoUi->mPrintList, SIGNAL(signalMoveDownItem()), this, SLOT(slotBtnPrintOrderDownClicked())); connect(d->photoUi->mPrintList, SIGNAL(signalMoveUpItem()), this, SLOT(slotBtnPrintOrderUpClicked())); connect(d->photoUi->mPrintList, SIGNAL(signalAddItems(QList)), this, SLOT(slotAddItems(QList))); connect(d->photoUi->mPrintList, SIGNAL(signalRemovedItems(QList)), this, SLOT(slotRemovingItems(QList))); connect(d->photoUi->mPrintList, SIGNAL(signalContextMenuRequested()), this, SLOT(slotContextMenuRequested())); connect(d->photoUi->mPrintList, SIGNAL(signalXMLSaveItem(QXmlStreamWriter&,int)), this, SLOT(slotXMLSaveItem(QXmlStreamWriter&,int))); connect(d->photoUi->mPrintList, SIGNAL(signalXMLCustomElements(QXmlStreamWriter&)), this, SLOT(slotXMLCustomElement(QXmlStreamWriter&))); connect(d->photoUi->mPrintList, SIGNAL(signalXMLLoadImageElement(QXmlStreamReader&)), this, SLOT(slotXMLLoadElement(QXmlStreamReader&))); connect(d->photoUi->mPrintList, SIGNAL(signalXMLCustomElements(QXmlStreamReader&)), this, SLOT(slotXMLCustomElement(QXmlStreamReader&))); // ----------------------------------- setPageWidget(d->photoUi); setLeftBottomPix(QIcon::fromTheme(QLatin1String("image-stack"))); slotOutputChanged(d->photoUi->m_printer_choice->itemHighlighted()); } AdvPrintPhotoPage::~AdvPrintPhotoPage() { delete d->printer; delete d->pageSetupDlg; delete d; } void AdvPrintPhotoPage::initializePage() { d->photoUi->mPrintList->listView()->clear(); if (d->settings->selMode == AdvPrintSettings::IMAGES) { d->photoUi->mPrintList->loadImagesFromCurrentSelection(); } else { d->wizard->setItemsList(d->settings->inputImages); } initPhotoSizes(d->printer->paperSize(QPrinter::Millimeter)); // restore photoSize if (d->settings->savedPhotoSize == i18n(CUSTOM_PAGE_LAYOUT_NAME)) { d->photoUi->ListPhotoSizes->setCurrentRow(0); } else { QList list = d->photoUi->ListPhotoSizes->findItems(d->settings->savedPhotoSize, Qt::MatchExactly); if (list.count()) d->photoUi->ListPhotoSizes->setCurrentItem(list[0]); else d->photoUi->ListPhotoSizes->setCurrentRow(0); } // reset preview page number d->settings->currentPreviewPage = 0; // create our photo sizes list d->wizard->previewPhotos(); int gid = d->photoUi->m_printer_choice->findText(d->settings->outputName(AdvPrintSettings::GIMP)); if (d->settings->gimpPath.isEmpty()) { // Gimp is not available : we disable the option. d->photoUi->m_printer_choice->setItemData(gid, false, Qt::UserRole-1); } int index = d->photoUi->m_printer_choice->findText(d->settings->printerName); if (index != -1) { d->photoUi->m_printer_choice->setCurrentIndex(index); } slotOutputChanged(d->photoUi->m_printer_choice->itemHighlighted()); d->photoUi->ListPhotoSizes->setIconSize(QSize(32, 32)); initPhotoSizes(d->printer->paperSize(QPrinter::Millimeter)); } bool AdvPrintPhotoPage::validatePage() { d->settings->inputImages = d->photoUi->mPrintList->imageUrls(); d->settings->printerName = d->photoUi->m_printer_choice->itemHighlighted(); if (d->photoUi->ListPhotoSizes->currentItem()) { d->settings->savedPhotoSize = d->photoUi->ListPhotoSizes->currentItem()->text(); } return true; } bool AdvPrintPhotoPage::isComplete() const { return (!d->photoUi->mPrintList->imageUrls().isEmpty() || !d->wizard->itemsList().isEmpty()); } QPrinter* AdvPrintPhotoPage::printer() const { return d->printer; } DImagesList* AdvPrintPhotoPage::imagesList() const { return d->photoUi->mPrintList; } Ui_AdvPrintPhotoPage* AdvPrintPhotoPage::ui() const { return d->photoUi; } void AdvPrintPhotoPage::slotOutputChanged(const QString& text) { if (AdvPrintSettings::outputNames().values().contains(text)) { delete d->printer; d->printer = new QPrinter(); d->printer->setOutputFormat(QPrinter::PdfFormat); } else // real printer { for (QList::iterator it = d->printerList.begin() ; it != d->printerList.end() ; ++it) { if (it->printerName() == text) { qCDebug(DIGIKAM_GENERAL_LOG) << "Chosen printer: " << it->printerName(); delete d->printer; d->printer = new QPrinter(*it); } } d->printer->setOutputFormat(QPrinter::NativeFormat); } // default no margins d->printer->setFullPage(true); d->printer->setPageMargins(0, 0, 0, 0, QPrinter::Millimeter); } void AdvPrintPhotoPage::slotXMLLoadElement(QXmlStreamReader& xmlReader) { if (d->settings->photos.size()) { // read image is the last. AdvPrintPhoto* const pPhoto = d->settings->photos[d->settings->photos.size()-1]; qCDebug(DIGIKAM_GENERAL_LOG) << " invoked " << xmlReader.name(); while (xmlReader.readNextStartElement()) { qCDebug(DIGIKAM_GENERAL_LOG) << pPhoto->m_url << " " << xmlReader.name(); if (xmlReader.name() == QLatin1String("pa_caption")) { //useless this item has been added now if (pPhoto->m_pAdvPrintCaptionInfo) delete pPhoto->m_pAdvPrintCaptionInfo; pPhoto->m_pAdvPrintCaptionInfo = new AdvPrintCaptionInfo(); // get all attributes and its value of a tag in attrs variable. QXmlStreamAttributes attrs = xmlReader.attributes(); // get value of each attribute from QXmlStreamAttributes QStringRef attr = attrs.value(QLatin1String("type")); bool ok; if (!attr.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << " found " << attr.toString(); pPhoto->m_pAdvPrintCaptionInfo->m_captionType = (AdvPrintSettings::CaptionType)attr.toString().toInt(&ok); } attr = attrs.value(QLatin1String("font")); if (!attr.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << " found " << attr.toString(); pPhoto->m_pAdvPrintCaptionInfo->m_captionFont.fromString(attr.toString()); } attr = attrs.value(QLatin1String("color")); if (!attr.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << " found " << attr.toString(); pPhoto->m_pAdvPrintCaptionInfo->m_captionColor.setNamedColor(attr.toString()); } attr = attrs.value(QLatin1String("size")); if (!attr.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << " found " << attr.toString(); pPhoto->m_pAdvPrintCaptionInfo->m_captionSize = attr.toString().toInt(&ok); } attr = attrs.value(QLatin1String("text")); if (!attr.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << " found " << attr.toString(); pPhoto->m_pAdvPrintCaptionInfo->m_captionText = attr.toString(); } } } } } void AdvPrintPhotoPage::slotXMLSaveItem(QXmlStreamWriter& xmlWriter, int itemIndex) { if (d->settings->photos.size()) { AdvPrintPhoto* const pPhoto = d->settings->photos[itemIndex]; // TODO: first and copies could be removed since they are not useful any more xmlWriter.writeAttribute(QLatin1String("first"), QString::fromUtf8("%1") .arg(pPhoto->m_first)); xmlWriter.writeAttribute(QLatin1String("copies"), QString::fromUtf8("%1") .arg(pPhoto->m_first ? pPhoto->m_copies : 0)); // additional info (caption... etc) if (pPhoto->m_pAdvPrintCaptionInfo) { xmlWriter.writeStartElement(QLatin1String("pa_caption")); xmlWriter.writeAttribute(QLatin1String("type"), QString::fromUtf8("%1").arg(pPhoto->m_pAdvPrintCaptionInfo->m_captionType)); xmlWriter.writeAttribute(QLatin1String("font"), pPhoto->m_pAdvPrintCaptionInfo->m_captionFont.toString()); xmlWriter.writeAttribute(QLatin1String("size"), QString::fromUtf8("%1").arg(pPhoto->m_pAdvPrintCaptionInfo->m_captionSize)); xmlWriter.writeAttribute(QLatin1String("color"), pPhoto->m_pAdvPrintCaptionInfo->m_captionColor.name()); xmlWriter.writeAttribute(QLatin1String("text"), pPhoto->m_pAdvPrintCaptionInfo->m_captionText); xmlWriter.writeEndElement(); // pa_caption } } } void AdvPrintPhotoPage::slotXMLCustomElement(QXmlStreamWriter& xmlWriter) { xmlWriter.writeStartElement(QLatin1String("pa_layout")); xmlWriter.writeAttribute(QLatin1String("Printer"), d->photoUi->m_printer_choice->itemHighlighted()); xmlWriter.writeAttribute(QLatin1String("PageSize"), QString::fromUtf8("%1").arg(d->printer->paperSize())); xmlWriter.writeAttribute(QLatin1String("PhotoSize"), d->photoUi->ListPhotoSizes->currentItem()->text()); xmlWriter.writeEndElement(); // pa_layout } void AdvPrintPhotoPage::slotContextMenuRequested() { if (d->settings->photos.size()) { int itemIndex = d->photoUi->mPrintList->listView()->currentIndex().row(); d->photoUi->mPrintList->listView()->blockSignals(true); QMenu menu(d->photoUi->mPrintList->listView()); QAction* const action = menu.addAction(i18n("Add again")); connect(action, SIGNAL(triggered()), this , SLOT(slotIncreaseCopies())); AdvPrintPhoto* const pPhoto = d->settings->photos[itemIndex]; qCDebug(DIGIKAM_GENERAL_LOG) << " copies " << pPhoto->m_copies << " first " << pPhoto->m_first; if (pPhoto->m_copies > 1 || !pPhoto->m_first) { QAction* const actionr = menu.addAction(i18n("Remove")); connect(actionr, SIGNAL(triggered()), this, SLOT(slotDecreaseCopies())); } menu.exec(QCursor::pos()); d->photoUi->mPrintList->listView()->blockSignals(false); } } void AdvPrintPhotoPage::slotIncreaseCopies() { if (d->settings->photos.size()) { QList list; DImagesListViewItem* const item = dynamic_cast(d->photoUi->mPrintList->listView()->currentItem()); if (!item) return; list.append(item->url()); qCDebug(DIGIKAM_GENERAL_LOG) << " Adding a copy of " << item->url(); d->photoUi->mPrintList->slotAddImages(list); } } void AdvPrintPhotoPage::slotDecreaseCopies() { if (d->settings->photos.size()) { DImagesListViewItem* const item = dynamic_cast (d->photoUi->mPrintList->listView()->currentItem()); if (!item) return; qCDebug(DIGIKAM_GENERAL_LOG) << " Removing a copy of " << item->url(); d->photoUi->mPrintList->slotRemoveItems(); } } void AdvPrintPhotoPage::slotAddItems(const QList& list) { if (list.count() == 0) { return; } QList urls; d->photoUi->mPrintList->blockSignals(true); for (QList::ConstIterator it = list.constBegin() ; it != list.constEnd() ; ++it) { QUrl imageUrl = *it; // Check if the new item already exist in the list. bool found = false; for (int i = 0 ; i < d->settings->photos.count() && !found ; ++i) { AdvPrintPhoto* const pCurrentPhoto = d->settings->photos.at(i); if (pCurrentPhoto && (pCurrentPhoto->m_url == imageUrl) && pCurrentPhoto->m_first) { pCurrentPhoto->m_copies++; found = true; AdvPrintPhoto* const pPhoto = new AdvPrintPhoto(*pCurrentPhoto); pPhoto->m_first = false; d->settings->photos.append(pPhoto); qCDebug(DIGIKAM_GENERAL_LOG) << "Added fileName: " << pPhoto->m_url.fileName() << " copy number " << pCurrentPhoto->m_copies; } } if (!found) { AdvPrintPhoto* const pPhoto = new AdvPrintPhoto(150, d->iface); pPhoto->m_url = *it; pPhoto->m_first = true; d->settings->photos.append(pPhoto); qCDebug(DIGIKAM_GENERAL_LOG) << "Added new fileName: " << pPhoto->m_url.fileName(); } } d->photoUi->mPrintList->blockSignals(false); d->photoUi->LblPhotoCount->setText(QString::number(d->settings->photos.count())); if (d->settings->photos.size()) { setComplete(true); } } void AdvPrintPhotoPage::slotRemovingItems(const QList& list) { if (list.count() == 0) { return; } d->photoUi->mPrintList->blockSignals(true); foreach (int itemIndex, list) { if (d->settings->photos.size() && itemIndex >= 0) { /// Debug data: found and copies bool found = false; int copies = 0; AdvPrintPhoto* const pPhotoToRemove = d->settings->photos.at(itemIndex); // photo to be removed could be: // 1) unique => just remove it // 2) first of n, => // search another with the same url // and set it a first and with a count to n-1 then remove it // 3) one of n, search the first one and set count to n-1 then remove it if (pPhotoToRemove && pPhotoToRemove->m_first) { if (pPhotoToRemove->m_copies > 0) { for (int i = 0 ; i < d->settings->photos.count() && !found ; ++i) { AdvPrintPhoto* const pCurrentPhoto = d->settings->photos.at(i); if (pCurrentPhoto && pCurrentPhoto->m_url == pPhotoToRemove->m_url) { pCurrentPhoto->m_copies = pPhotoToRemove->m_copies - 1; copies = pCurrentPhoto->m_copies; pCurrentPhoto->m_first = true; found = true; } } } // otherwise it's unique } else if (pPhotoToRemove) { for (int i = 0 ; i < d->settings->photos.count() && !found ; ++i) { AdvPrintPhoto* const pCurrentPhoto = d->settings->photos.at(i); if (pCurrentPhoto && pCurrentPhoto->m_url == pPhotoToRemove->m_url && pCurrentPhoto->m_first) { pCurrentPhoto->m_copies--; copies = pCurrentPhoto->m_copies; found = true; } } } else { qCDebug(DIGIKAM_GENERAL_LOG) << " NULL AdvPrintPhoto object "; return; } if (pPhotoToRemove) { qCDebug(DIGIKAM_GENERAL_LOG) << "Removed fileName: " << pPhotoToRemove->m_url.fileName() << " copy number " << copies; } d->settings->photos.removeAt(itemIndex); delete pPhotoToRemove; } } d->wizard->previewPhotos(); d->photoUi->mPrintList->blockSignals(false); d->photoUi->LblPhotoCount->setText(QString::number(d->settings->photos.count())); if (d->settings->photos.isEmpty()) { // No photos => disabling next button (e.g. crop page) setComplete(false); } } void AdvPrintPhotoPage::slotBtnPrintOrderDownClicked() { d->photoUi->mPrintList->blockSignals(true); int currentIndex = d->photoUi->mPrintList->listView()->currentIndex().row(); qCDebug(DIGIKAM_GENERAL_LOG) << "Moved photo " << currentIndex - 1 << " to " << currentIndex; d->settings->photos.swap(currentIndex, currentIndex - 1); d->photoUi->mPrintList->blockSignals(false); d->wizard->previewPhotos(); } void AdvPrintPhotoPage::slotBtnPrintOrderUpClicked() { d->photoUi->mPrintList->blockSignals(true); int currentIndex = d->photoUi->mPrintList->listView()->currentIndex().row(); qCDebug(DIGIKAM_GENERAL_LOG) << "Moved photo " << currentIndex << " to " << currentIndex + 1; d->settings->photos.swap(currentIndex, currentIndex + 1); d->photoUi->mPrintList->blockSignals(false); d->wizard->previewPhotos(); } void AdvPrintPhotoPage::slotXMLCustomElement(QXmlStreamReader& xmlReader) { qCDebug(DIGIKAM_GENERAL_LOG) << " invoked " << xmlReader.name(); while (!xmlReader.atEnd()) { if (xmlReader.isStartElement() && xmlReader.name() == QLatin1String("pa_layout")) { bool ok; QXmlStreamAttributes attrs = xmlReader.attributes(); // get value of each attribute from QXmlStreamAttributes QStringRef attr = attrs.value(QLatin1String("Printer")); if (!attr.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << " found " << attr.toString(); int index = d->photoUi->m_printer_choice->findText(attr.toString()); if (index != -1) { d->photoUi->m_printer_choice->setCurrentIndex(index); } slotOutputChanged(d->photoUi->m_printer_choice->itemHighlighted()); } attr = attrs.value(QLatin1String("PageSize")); if (!attr.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << " found " << attr.toString(); QPrinter::PaperSize paperSize = (QPrinter::PaperSize)attr.toString().toInt(&ok); d->printer->setPaperSize(paperSize); } attr = attrs.value(QLatin1String("PhotoSize")); if (!attr.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << " found " << attr.toString(); d->settings->savedPhotoSize = attr.toString(); } } xmlReader.readNext(); } // reset preview page number d->settings->currentPreviewPage = 0; initPhotoSizes(d->printer->paperSize(QPrinter::Millimeter)); QList list = d->photoUi->ListPhotoSizes->findItems(d->settings->savedPhotoSize, Qt::MatchExactly); if (list.count()) { qCDebug(DIGIKAM_GENERAL_LOG) << " PhotoSize " << list[0]->text(); d->photoUi->ListPhotoSizes->setCurrentItem(list[0]); } else { d->photoUi->ListPhotoSizes->setCurrentRow(0); } d->wizard->previewPhotos(); } void AdvPrintPhotoPage::slotBtnPreviewPageDownClicked() { if (d->settings->currentPreviewPage == 0) return; d->settings->currentPreviewPage--; d->wizard->previewPhotos(); } void AdvPrintPhotoPage::slotBtnPreviewPageUpClicked() { if (d->settings->currentPreviewPage == getPageCount() - 1) return; d->settings->currentPreviewPage++; d->wizard->previewPhotos(); } int AdvPrintPhotoPage::getPageCount() const { int pageCount = 0; int photoCount = d->settings->photos.count(); if (photoCount > 0) { // get the selected layout AdvPrintPhotoSize* const s = d->settings->photosizes.at(d->photoUi->ListPhotoSizes->currentRow()); // how many pages? Recall that the first layout item is the paper size int photosPerPage = s->m_layouts.count() - 1; int remainder = photoCount % photosPerPage; int emptySlots = 0; if (remainder > 0) emptySlots = photosPerPage - remainder; pageCount = photoCount / photosPerPage; if (emptySlots > 0) pageCount++; } return pageCount; } void AdvPrintPhotoPage::createPhotoGrid(AdvPrintPhotoSize* const p, int pageWidth, int pageHeight, int rows, int columns, TemplateIcon* const iconpreview) { int MARGIN = (int)(((double)pageWidth + (double)pageHeight) / 2.0 * 0.04 + 0.5); int GAP = MARGIN / 4; int photoWidth = (pageWidth - (MARGIN * 2) - ((columns - 1) * GAP)) / columns; int photoHeight = (pageHeight - (MARGIN * 2) - ((rows - 1) * GAP)) / rows; int row = 0; for (int y = MARGIN ; (row < rows) && (y < (pageHeight - MARGIN)) ; y += photoHeight + GAP) { int col = 0; for (int x = MARGIN ; (col < columns) && (x < (pageWidth - MARGIN)) ; x += photoWidth + GAP) { p->m_layouts.append(new QRect(x, y, photoWidth, photoHeight)); iconpreview->fillRect(x, y, photoWidth, photoHeight, Qt::color1); col++; } row++; } } void AdvPrintPhotoPage::slotListPhotoSizesSelected() { AdvPrintPhotoSize* s = 0; QSizeF size, sizeManaged; // TODO FREE STYLE // check if layout is managed by templates or free one // get the selected layout int curr = d->photoUi->ListPhotoSizes->currentRow(); QListWidgetItem* item = d->photoUi->ListPhotoSizes->item(curr); // if custom page layout we launch a dialog to choose what kind if (item->text() == i18n(CUSTOM_PAGE_LAYOUT_NAME)) { // check if a custom layout has already been added if (curr >= 0 && curr < d->settings->photosizes.size()) { s = d->settings->photosizes.at(curr); d->settings->photosizes.removeAt(curr); delete s; s = NULL; } QPointer custDlg = new AdvPrintCustomLayoutDlg(this); custDlg->readSettings(); custDlg->exec(); custDlg->saveSettings(); // get parameters from dialog size = d->settings->pageSize; int scaleValue = 10; // 0.1 mm // convert to mm if (custDlg->m_photoUnits->currentText() == i18n("inches")) { size /= 25.4; scaleValue = 1000; } else if (custDlg->m_photoUnits->currentText() == i18n("cm")) { size /= 10; scaleValue = 100; } sizeManaged = size * scaleValue; s = new AdvPrintPhotoSize; TemplateIcon iconpreview(80, sizeManaged.toSize()); iconpreview.begin(); if (custDlg->m_photoGridCheck->isChecked()) { // custom photo grid int rows = custDlg->m_gridRows->value(); int columns = custDlg->m_gridColumns->value(); s->m_layouts.append(new QRect(0, 0, (int)sizeManaged.width(), (int)sizeManaged.height())); s->m_autoRotate = custDlg->m_autorotate->isChecked(); s->m_label = item->text(); s->m_dpi = 0; int pageWidth = (int)(size.width()) * scaleValue; int pageHeight = (int)(size.height()) * scaleValue; createPhotoGrid(s, pageWidth, pageHeight, rows, columns, &iconpreview); } else if (custDlg->m_fitAsManyCheck->isChecked()) { int width = custDlg->m_photoWidth->value(); int height = custDlg->m_photoHeight->value(); //photo size must be less than page size static const float round_value = 0.01F; if ((height > (size.height() + round_value) || width > (size.width() + round_value))) { qCDebug(DIGIKAM_GENERAL_LOG) << "photo size " << QSize(width, height) << "> page size " << size; delete s; s = NULL; } else { // fit as many photos of given size as possible s->m_layouts.append(new QRect(0, 0, (int)sizeManaged.width(), (int)sizeManaged.height())); s->m_autoRotate = custDlg->m_autorotate->isChecked(); s->m_label = item->text(); s->m_dpi = 0; int nColumns = int(size.width() / width); int nRows = int(size.height() / height); int spareWidth = int(size.width()) % width; // check if there's no room left to separate photos if (nColumns > 1 && spareWidth == 0) { nColumns -= 1; spareWidth = width; } int spareHeight = int(size.height()) % height; // check if there's no room left to separate photos if (nRows > 1 && spareHeight == 0) { nRows -= 1; spareHeight = height; } if (nRows > 0 && nColumns > 0) { // n photos => dx1, photo1, dx2, photo2,... photoN, dxN+1 int dx = spareWidth * scaleValue / (nColumns + 1); int dy = spareHeight * scaleValue / (nRows + 1); int photoX = 0; int photoY = 0; width *= scaleValue; height *= scaleValue; for (int row = 0 ; row < nRows ; ++row) { photoY = dy * (row + 1) + (row * height); for (int col = 0 ; col < nColumns ; ++col) { photoX = dx * (col + 1) + (col * width); qCDebug(DIGIKAM_GENERAL_LOG) << "photo at P(" << photoX << ", " << photoY << ") size(" << width << ", " << height; s->m_layouts.append(new QRect(photoX, photoY, width, height)); iconpreview.fillRect(photoX, photoY, width, height, Qt::color1); } } } else { qCDebug(DIGIKAM_GENERAL_LOG) << "I cannot go on, rows " << nRows << "> columns " << nColumns; delete s; s = NULL; } } } else { // Atckin's layout } // TODO not for Atckin's layout iconpreview.end(); if (s) { s->m_icon = iconpreview.getIcon(); d->settings->photosizes.append(s); } delete custDlg; } else { s = d->settings->photosizes.at(curr); } if (!s) { // change position to top d->photoUi->ListPhotoSizes->blockSignals(true); d->photoUi->ListPhotoSizes->setCurrentRow(0, QItemSelectionModel::Select); d->photoUi->ListPhotoSizes->blockSignals(false); } // reset preview page number d->settings->currentPreviewPage = 0; d->wizard->previewPhotos(); } void AdvPrintPhotoPage::slotPageSetup() { delete d->pageSetupDlg; QString lastSize = d->photoUi->ListPhotoSizes->currentItem()->text(); d->pageSetupDlg = new QPageSetupDialog(d->printer, this); int ret = d->pageSetupDlg->exec(); if (ret == QDialog::Accepted) { QPrinter* const printer = d->pageSetupDlg->printer(); qCDebug(DIGIKAM_GENERAL_LOG) << "Dialog exit, new size " << printer->paperSize(QPrinter::Millimeter) << " internal size " << d->printer->paperSize(QPrinter::Millimeter); qreal left, top, right, bottom; d->printer->getPageMargins(&left, &top, &right, &bottom, QPrinter::Millimeter); qCDebug(DIGIKAM_GENERAL_LOG) << "Dialog exit, new margins: left " << left << " right " << right << " top " << top << " bottom " << bottom; // next should be useless invoke once changing wizard page //d->wizard->initPhotoSizes(d->printer.paperSize(QPrinter::Millimeter)); //d->settings->pageSize = d->printer.paperSize(QPrinter::Millimeter); #ifdef DEBUG qCDebug(DIGIKAM_GENERAL_LOG) << " dialog exited num of copies: " << printer->numCopies() << " inside: " << d->printer->numCopies(); qCDebug(DIGIKAM_GENERAL_LOG) << " dialog exited from : " << printer->fromPage() << " to: " << d->printer->toPage(); #endif } // Fix the page size dialog and preview PhotoPage initPhotoSizes(d->printer->paperSize(QPrinter::Millimeter)); // restore photoSize if (d->settings->savedPhotoSize == i18n(CUSTOM_PAGE_LAYOUT_NAME)) { d->photoUi->ListPhotoSizes->setCurrentRow(0); } else { QList list = d->photoUi->ListPhotoSizes->findItems(lastSize, Qt::MatchExactly); if (list.count()) d->photoUi->ListPhotoSizes->setCurrentItem(list[0]); else d->photoUi->ListPhotoSizes->setCurrentRow(0); } // create our photo sizes list d->wizard->previewPhotos(); } void AdvPrintPhotoPage::manageBtnPreviewPage() { if (d->settings->photos.isEmpty()) { d->photoUi->BtnPreviewPageDown->setEnabled(false); d->photoUi->BtnPreviewPageUp->setEnabled(false); } else { d->photoUi->BtnPreviewPageDown->setEnabled(true); d->photoUi->BtnPreviewPageUp->setEnabled(true); if (d->settings->currentPreviewPage == 0) { d->photoUi->BtnPreviewPageDown->setEnabled(false); } if ((d->settings->currentPreviewPage + 1) == getPageCount()) { d->photoUi->BtnPreviewPageUp->setEnabled(false); } } } void AdvPrintPhotoPage::initPhotoSizes(const QSizeF& pageSize) { qCDebug(DIGIKAM_GENERAL_LOG) << "New page size " << pageSize << ", old page size " << d->settings->pageSize; // don't refresh anything if we haven't changed page sizes. if (pageSize == d->settings->pageSize) return; d->settings->pageSize = pageSize; // cleaning pageSize memory before invoking clear() for (int i = 0 ; i < d->settings->photosizes.count() ; ++i) delete d->settings->photosizes.at(i); d->settings->photosizes.clear(); // get template-files and parse them QDir dir(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/templates"), QStandardPaths::LocateDirectory)); const QStringList list = dir.entryList(QStringList() << QLatin1String("*.xml")); qCDebug(DIGIKAM_GENERAL_LOG) << "Template XML files list: " << list; foreach(const QString& fn, list) { - parseTemplateFile(dir.absolutePath() + QLatin1String("/") + fn, pageSize); + parseTemplateFile(dir.absolutePath() + QLatin1Char('/') + fn, pageSize); } qCDebug(DIGIKAM_GENERAL_LOG) << "photosizes count() =" << d->settings->photosizes.count(); qCDebug(DIGIKAM_GENERAL_LOG) << "photosizes isEmpty() =" << d->settings->photosizes.isEmpty(); if (d->settings->photosizes.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << "Empty photoSize-list, create default size"; // There is no valid page size yet. Create a default page (B10) to prevent crashes. AdvPrintPhotoSize* const p = new AdvPrintPhotoSize; // page size: B10 (32 x 45 mm) p->m_layouts.append(new QRect(0, 0, 3200, 4500)); p->m_layouts.append(new QRect(0, 0, 3200, 4500)); // add to the list d->settings->photosizes.append(p); } // load the photo sizes into the listbox d->photoUi->ListPhotoSizes->blockSignals(true); d->photoUi->ListPhotoSizes->clear(); QList::iterator it; for (it = d->settings->photosizes.begin() ; it != d->settings->photosizes.end() ; ++it) { AdvPrintPhotoSize* const s = static_cast(*it); if (s) { QListWidgetItem* const pWItem = new QListWidgetItem(s->m_label); pWItem->setIcon(s->m_icon); d->photoUi->ListPhotoSizes->addItem(pWItem); } } // Adding custom choice QListWidgetItem* const pWItem = new QListWidgetItem(i18n(CUSTOM_PAGE_LAYOUT_NAME)); TemplateIcon ti(80, pageSize.toSize()); ti.begin(); QPainter& painter = ti.getPainter(); painter.setPen(Qt::color1); painter.drawText(painter.viewport(), Qt::AlignCenter, i18n("Custom\nlayout")); ti.end(); pWItem->setIcon(ti.getIcon()); d->photoUi->ListPhotoSizes->addItem(pWItem); d->photoUi->ListPhotoSizes->blockSignals(false); d->photoUi->ListPhotoSizes->setCurrentRow(0, QItemSelectionModel::Select); } void AdvPrintPhotoPage::parseTemplateFile(const QString& fn, const QSizeF& pageSize) { QDomDocument doc(QLatin1String("mydocument")); qCDebug(DIGIKAM_GENERAL_LOG) << " XXX: " << fn; if (fn.isEmpty()) { return; } QFile file(fn); if (!file.open(QIODevice::ReadOnly)) return; if (!doc.setContent(&file)) { file.close(); return; } file.close(); AdvPrintPhotoSize* p = 0; // print out the element names of all elements that are direct children // of the outermost element. QDomElement docElem = doc.documentElement(); qCDebug(DIGIKAM_GENERAL_LOG) << docElem.tagName(); // the node really is an element. QSizeF size; QString unit; int scaleValue; QDomNode n = docElem.firstChild(); while (!n.isNull()) { size = QSizeF(0, 0); scaleValue = 10; // 0.1 mm QDomElement e = n.toElement(); // try to convert the node to an element. if (!e.isNull()) { if (e.tagName() == QLatin1String("paper")) { size = QSizeF(e.attribute(QLatin1String("width"), QLatin1String("0")).toFloat(), e.attribute(QLatin1String("height"), QLatin1String("0")).toFloat()); unit = e.attribute(QLatin1String("unit"), QLatin1String("mm")); qCDebug(DIGIKAM_GENERAL_LOG) << e.tagName() << QLatin1String(" name=") << e.attribute(QLatin1String("name"), QLatin1String("??")) << " size= " << size << " unit= " << unit; if (size == QSizeF(0.0, 0.0) && size == pageSize) { // skipping templates without page size since pageSize is not set n = n.nextSibling(); continue; } else if (unit != QLatin1String("mm") && size != QSizeF(0.0, 0.0)) // "cm", "inches" or "inch" { // convert to mm if (unit == QLatin1String("inches") || unit == QLatin1String("inch")) { size *= 25.4; scaleValue = 1000; qCDebug(DIGIKAM_GENERAL_LOG) << "template size " << size << " page size " << pageSize; } else if (unit == QLatin1String("cm")) { size *= 10; scaleValue = 100; qCDebug(DIGIKAM_GENERAL_LOG) << "template size " << size << " page size " << pageSize; } else { qCWarning(DIGIKAM_GENERAL_LOG) << "Wrong unit " << unit << " skipping layout"; n = n.nextSibling(); continue; } } static const float round_value = 0.01F; if (size == QSizeF(0, 0)) { size = pageSize; unit = QLatin1String("mm"); } else if (pageSize != QSizeF(0, 0) && (size.height() > (pageSize.height() + round_value) || size.width() > (pageSize.width() + round_value))) { qCDebug(DIGIKAM_GENERAL_LOG) << "skipping size " << size << " page size " << pageSize; // skipping layout it can't fit n = n.nextSibling(); continue; } // Next templates are good qCDebug(DIGIKAM_GENERAL_LOG) << "layout size " << size << " page size " << pageSize; QDomNode np = e.firstChild(); while (!np.isNull()) { QDomElement ep = np.toElement(); // try to convert the node to an element. if (!ep.isNull()) { if (ep.tagName() == QLatin1String("template")) { p = new AdvPrintPhotoSize; QSizeF sizeManaged; // set page size if (pageSize == QSizeF(0, 0)) { sizeManaged = size * scaleValue; } else if (unit == QLatin1String("inches") || unit == QLatin1String("inch")) { sizeManaged = pageSize * scaleValue / 25.4; } else { sizeManaged = pageSize * 10; } p->m_layouts.append(new QRect(0, 0, (int)sizeManaged.width(), (int)sizeManaged.height())); // create a small preview of the template TemplateIcon iconpreview(80, sizeManaged.toSize()); iconpreview.begin(); QString desktopFileName = ep.attribute(QLatin1String("name"), QLatin1String("XXX")) + QLatin1String(".desktop"); QDir dir(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/templates"), QStandardPaths::LocateDirectory)); const QStringList list = dir.entryList(QStringList() << desktopFileName); qCDebug(DIGIKAM_GENERAL_LOG) << "Template desktop files list: " << list; QStringList::ConstIterator it = list.constBegin(); QStringList::ConstIterator end = list.constEnd(); if (it != end) { p->m_label = KDesktopFile(dir.absolutePath() + QLatin1String("/") + *it).readName(); } else { p->m_label = ep.attribute(QLatin1String("name"), QLatin1String("XXX")); qCWarning(DIGIKAM_GENERAL_LOG) << "missed template translation " << desktopFileName; } p->m_dpi = ep.attribute(QLatin1String("dpi"), QLatin1String("0")).toInt(); p->m_autoRotate = (ep.attribute(QLatin1String("autorotate"), QLatin1String("false")) == QLatin1String("true")) ? true : false; QDomNode nt = ep.firstChild(); while (!nt.isNull()) { QDomElement et = nt.toElement(); // try to convert the node to an element. if (!et.isNull()) { if (et.tagName() == QLatin1String("photo")) { float value = et.attribute(QLatin1String("width"), QLatin1String("0")).toFloat(); int width = (int)((value == 0 ? size.width() : value) * scaleValue); value = et.attribute(QLatin1String("height"), QLatin1String("0")).toFloat(); int height = (int)((value == 0 ? size.height() : value) * scaleValue); int photoX = (int)((et.attribute(QLatin1String("x"), QLatin1String("0")).toFloat() * scaleValue)); int photoY = (int)((et.attribute(QLatin1String("y"), QLatin1String("0")).toFloat() * scaleValue)); p->m_layouts.append(new QRect(photoX, photoY, width, height)); iconpreview.fillRect(photoX, photoY, width, height, Qt::color1); } else if (et.tagName() == QLatin1String("photogrid")) { float value = et.attribute(QLatin1String("pageWidth"), QLatin1String("0")).toFloat(); int pageWidth = (int)((value == 0 ? size.width() : value) * scaleValue); value = et.attribute(QLatin1String("pageHeight"), QLatin1String("0")).toFloat(); int pageHeight = (int)((value == 0 ? size.height() : value) * scaleValue); int rows = et.attribute(QLatin1String("rows"), QLatin1String("0")).toInt(); int columns = et.attribute(QLatin1String("columns"), QLatin1String("0")).toInt(); if (rows > 0 && columns > 0) { createPhotoGrid(p, pageWidth, pageHeight, rows, columns, &iconpreview); } else { qCWarning(DIGIKAM_GENERAL_LOG) << " Wrong grid configuration, rows " << rows << ", columns " << columns; } } else { qCDebug(DIGIKAM_GENERAL_LOG) << " " << et.tagName(); } } nt = nt.nextSibling(); } iconpreview.end(); p->m_icon = iconpreview.getIcon(); d->settings->photosizes.append(p); } else { qCDebug(DIGIKAM_GENERAL_LOG) << "? " << ep.tagName() << " attr=" << ep.attribute(QLatin1String("name"), QLatin1String("??")); } } np = np.nextSibling(); } } else { qCDebug(DIGIKAM_GENERAL_LOG) << "??" << e.tagName() << " name=" << e.attribute(QLatin1String("name"), QLatin1String("??")); } } n = n.nextSibling(); } } } // namespace Digikam diff --git a/core/utilities/assistants/webservices/dropbox/dbwindow.cpp b/core/utilities/assistants/webservices/dropbox/dbwindow.cpp index 77fe3b130b..50d8dca9fd 100644 --- a/core/utilities/assistants/webservices/dropbox/dbwindow.cpp +++ b/core/utilities/assistants/webservices/dropbox/dbwindow.cpp @@ -1,463 +1,463 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2013-11-18 * Description : a tool to export images to Dropbox web service * * Copyright (C) 2013 by Pankaj Kumar * Copyright (C) 2013-2018 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 "dbwindow.h" // Qt includes #include #include #include #include #include // KDE includes #include #include #include // Local includes #include "digikam_debug.h" #include "dimageslist.h" #include "digikam_version.h" #include "dbtalker.h" #include "dbitem.h" #include "dbnewalbumdlg.h" #include "dbwidget.h" namespace Digikam { class DBWindow::Private { public: explicit Private() { imagesCount = 0; imagesTotal = 0; widget = 0; albumDlg = 0; talker = 0; } unsigned int imagesCount; unsigned int imagesTotal; DBWidget* widget; DBNewAlbumDlg* albumDlg; DBTalker* talker; QString currentAlbumName; QList transferQueue; }; DBWindow::DBWindow(DInfoInterface* const iface, QWidget* const /*parent*/) : WSToolDialog(0), d(new Private) { d->widget = new DBWidget(this, iface, QLatin1String("Dropbox")); d->widget->imagesList()->setIface(iface); setMainWidget(d->widget); setModal(false); setWindowTitle(i18n("Export to Dropbox")); startButton()->setText(i18n("Start Upload")); startButton()->setToolTip(i18n("Start upload to Dropbox")); d->widget->setMinimumSize(700, 500); connect(d->widget->imagesList(), SIGNAL(signalImageListChanged()), this, SLOT(slotImageListChanged())); connect(d->widget->getChangeUserBtn(), SIGNAL(clicked()), this, SLOT(slotUserChangeRequest())); connect(d->widget->getNewAlbmBtn(), SIGNAL(clicked()), this, SLOT(slotNewAlbumRequest())); connect(d->widget->getReloadBtn(), SIGNAL(clicked()), this, SLOT(slotReloadAlbumsRequest())); connect(startButton(), SIGNAL(clicked()), this, SLOT(slotStartTransfer())); d->albumDlg = new DBNewAlbumDlg(this, QLatin1String("Dropbox")); d->talker = new DBTalker(this); connect(d->talker, SIGNAL(signalBusy(bool)), this, SLOT(slotBusy(bool))); connect(d->talker, SIGNAL(signalLinkingFailed()), this, SLOT(slotSignalLinkingFailed())); connect(d->talker, SIGNAL(signalLinkingSucceeded()), this, SLOT(slotSignalLinkingSucceeded())); connect(d->talker, SIGNAL(signalSetUserName(QString)), this, SLOT(slotSetUserName(QString))); connect(d->talker, SIGNAL(signalListAlbumsFailed(QString)), this, SLOT(slotListAlbumsFailed(QString))); connect(d->talker, SIGNAL(signalListAlbumsDone(QList >)), // krazy:exclude=normalize this, SLOT(slotListAlbumsDone(QList >))); // krazy:exclude=normalize connect(d->talker, SIGNAL(signalCreateFolderFailed(QString)), this, SLOT(slotCreateFolderFailed(QString))); connect(d->talker, SIGNAL(signalCreateFolderSucceeded()), this, SLOT(slotCreateFolderSucceeded())); connect(d->talker, SIGNAL(signalAddPhotoFailed(QString)), this, SLOT(slotAddPhotoFailed(QString))); connect(d->talker, SIGNAL(signalAddPhotoSucceeded()), this, SLOT(slotAddPhotoSucceeded())); connect(this, SIGNAL(finished(int)), this, SLOT(slotFinished())); readSettings(); buttonStateChange(false); d->talker->link(); } DBWindow::~DBWindow() { delete d->widget; delete d->albumDlg; delete d->talker; delete d; } void DBWindow::setItemsList(const QList& urls) { d->widget->imagesList()->slotAddImages(urls); } void DBWindow::reactivate() { d->widget->imagesList()->loadImagesFromCurrentSelection(); d->widget->progressBar()->hide(); show(); } void DBWindow::readSettings() { KConfig config; KConfigGroup grp = config.group("Dropbox Settings"); d->currentAlbumName = grp.readEntry("Current Album",QString()); if (grp.readEntry("Resize", false)) { d->widget->getResizeCheckBox()->setChecked(true); d->widget->getDimensionSpB()->setEnabled(true); d->widget->getImgQualitySpB()->setEnabled(true); } else { d->widget->getResizeCheckBox()->setChecked(false); d->widget->getDimensionSpB()->setEnabled(false); d->widget->getImgQualitySpB()->setEnabled(false); } d->widget->getDimensionSpB()->setValue(grp.readEntry("Maximum Width", 1600)); d->widget->getImgQualitySpB()->setValue(grp.readEntry("Image Quality", 90)); winId(); KConfigGroup dialogGroup = config.group("Dropbox Export Dialog"); KWindowConfig::restoreWindowSize(windowHandle(), dialogGroup); resize(windowHandle()->size()); } void DBWindow::writeSettings() { KConfig config; KConfigGroup grp = config.group("Dropbox Settings"); grp.writeEntry("Current Album", d->currentAlbumName); grp.writeEntry("Resize", d->widget->getResizeCheckBox()->isChecked()); grp.writeEntry("Maximum Width", d->widget->getDimensionSpB()->value()); grp.writeEntry("Image Quality", d->widget->getImgQualitySpB()->value()); KConfigGroup dialogGroup = config.group("Dropbox Export Dialog"); KWindowConfig::saveWindowSize(windowHandle(), dialogGroup); config.sync(); } void DBWindow::slotSetUserName(const QString& msg) { d->widget->updateLabels(msg, QLatin1String("")); } void DBWindow::slotListAlbumsDone(const QList >& list) { d->widget->getAlbumsCoB()->clear(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "slotListAlbumsDone:" << list.size(); for (int i = 0 ; i < list.size() ; i++) { d->widget->getAlbumsCoB()->addItem( QIcon::fromTheme(QLatin1String("system-users")), list.value(i).second, list.value(i).first); if (d->currentAlbumName == list.value(i).first) { d->widget->getAlbumsCoB()->setCurrentIndex(i); } } buttonStateChange(true); d->talker->getUserName(); } void DBWindow::slotBusy(bool val) { if (val) { setCursor(Qt::WaitCursor); d->widget->getChangeUserBtn()->setEnabled(false); buttonStateChange(false); } else { setCursor(Qt::ArrowCursor); d->widget->getChangeUserBtn()->setEnabled(true); buttonStateChange(true); } } void DBWindow::slotStartTransfer() { d->widget->imagesList()->clearProcessedStatus(); if (d->widget->imagesList()->imageUrls().isEmpty()) { QMessageBox::critical(this, i18nc("@title:window", "Error"), i18n("No image selected. Please select which images should be uploaded.")); return; } if (!(d->talker->authenticated())) { if (QMessageBox::question(this, i18n("Login Failed"), i18n("Authentication failed. Do you want to try again?")) == QMessageBox::Yes) { d->talker->link(); return; } else { return; } } d->transferQueue = d->widget->imagesList()->imageUrls(); if (d->transferQueue.isEmpty()) { return; } d->currentAlbumName = d->widget->getAlbumsCoB()->itemData(d->widget->getAlbumsCoB()->currentIndex()).toString(); d->imagesTotal = d->transferQueue.count(); d->imagesCount = 0; d->widget->progressBar()->setFormat(i18n("%v / %m")); d->widget->progressBar()->setMaximum(d->imagesTotal); d->widget->progressBar()->setValue(0); d->widget->progressBar()->show(); d->widget->progressBar()->progressScheduled(i18n("Dropbox export"), true, true); d->widget->progressBar()->progressThumbnailChanged( QIcon(QLatin1String("dropbox")).pixmap(22, 22)); uploadNextPhoto(); } void DBWindow::uploadNextPhoto() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "uploadNextPhoto:" << d->transferQueue.count(); if (d->transferQueue.isEmpty()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "empty"; d->widget->progressBar()->progressCompleted(); return; } QString imgPath = d->transferQueue.first().toLocalFile(); - QString temp = d->currentAlbumName + QLatin1String("/"); + QString temp = d->currentAlbumName + QLatin1Char('/'); bool res = d->talker->addPhoto(imgPath, temp, d->widget->getResizeCheckBox()->isChecked(), d->widget->getDimensionSpB()->value(), d->widget->getImgQualitySpB()->value()); if (!res) { slotAddPhotoFailed(QLatin1String("")); return; } } void DBWindow::slotAddPhotoFailed(const QString& msg) { if (QMessageBox::question(this, i18n("Uploading Failed"), i18n("Failed to upload photo to Dropbox." "\n%1\n" "Do you want to continue?", msg)) != QMessageBox::Yes) { d->transferQueue.clear(); d->widget->progressBar()->hide(); } else { d->transferQueue.removeFirst(); d->imagesTotal--; d->widget->progressBar()->setMaximum(d->imagesTotal); d->widget->progressBar()->setValue(d->imagesCount); uploadNextPhoto(); } } void DBWindow::slotAddPhotoSucceeded() { // Remove photo uploaded from the list d->widget->imagesList()->removeItemByUrl(d->transferQueue.first()); d->transferQueue.removeFirst(); d->imagesCount++; d->widget->progressBar()->setMaximum(d->imagesTotal); d->widget->progressBar()->setValue(d->imagesCount); uploadNextPhoto(); } void DBWindow::slotImageListChanged() { startButton()->setEnabled(!(d->widget->imagesList()->imageUrls().isEmpty())); } void DBWindow::slotNewAlbumRequest() { if (d->albumDlg->exec() == QDialog::Accepted) { DBFolder newFolder; d->albumDlg->getFolderTitle(newFolder); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "slotNewAlbumRequest:" << newFolder.title; d->currentAlbumName = d->widget->getAlbumsCoB()->itemData(d->widget->getAlbumsCoB()->currentIndex()).toString(); QString temp = d->currentAlbumName + newFolder.title; d->talker->createFolder(temp); } } void DBWindow::slotReloadAlbumsRequest() { d->talker->listFolders(); } void DBWindow::slotSignalLinkingFailed() { slotSetUserName(QLatin1String("")); d->widget->getAlbumsCoB()->clear(); if (QMessageBox::question(this, i18n("Login Failed"), i18n("Authentication failed. Do you want to try again?")) == QMessageBox::Yes) { d->talker->link(); } } void DBWindow::slotSignalLinkingSucceeded() { d->talker->listFolders(); } void DBWindow::slotListAlbumsFailed(const QString& msg) { QMessageBox::critical(this, QString(), i18n("Dropbox call failed:\n%1", msg)); } void DBWindow::slotCreateFolderFailed(const QString& msg) { QMessageBox::critical(this, QString(), i18n("Dropbox call failed:\n%1", msg)); } void DBWindow::slotCreateFolderSucceeded() { d->talker->listFolders(); } void DBWindow::slotTransferCancel() { d->transferQueue.clear(); d->widget->progressBar()->hide(); d->talker->cancel(); } void DBWindow::slotUserChangeRequest() { slotSetUserName(QLatin1String("")); d->widget->getAlbumsCoB()->clear(); d->talker->unLink(); d->talker->link(); } void DBWindow::buttonStateChange(bool state) { d->widget->getNewAlbmBtn()->setEnabled(state); d->widget->getReloadBtn()->setEnabled(state); startButton()->setEnabled(state); } void DBWindow::slotFinished() { writeSettings(); d->widget->imagesList()->listView()->clear(); } void DBWindow::closeEvent(QCloseEvent* e) { if (!e) { return; } slotFinished(); e->accept(); } } // namespace Digikam diff --git a/core/utilities/assistants/webservices/flickr/flickrwindow.cpp b/core/utilities/assistants/webservices/flickr/flickrwindow.cpp index 9578531ca2..180b666dcd 100644 --- a/core/utilities/assistants/webservices/flickr/flickrwindow.cpp +++ b/core/utilities/assistants/webservices/flickr/flickrwindow.cpp @@ -1,869 +1,875 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2005-17-06 * Description : a tool to export images to Flickr web service * * Copyright (C) 2005-2008 by Vardhman Jain * Copyright (C) 2008-2018 by Gilles Caulier * Copyright (C) 2009 by Luka Renko * * 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 "flickrwindow.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include // Local includes #include "dprogresswdg.h" #include "flickrtalker.h" #include "flickritem.h" #include "flickrlist.h" #include "wsselectuserdlg.h" #include "digikam_debug.h" #include "flickrnewalbumdlg.h" #include "previewloadthread.h" #include "flickrwidget_p.h" namespace Digikam { class FlickrWindow::Private { public: explicit Private() { uploadCount = 0; uploadTotal = 0; newAlbumBtn = 0; changeUserButton = 0; removeAccount = 0; albumsListComboBox = 0; publicCheckBox = 0; familyCheckBox = 0; friendsCheckBox = 0; exportHostTagsCheckBox = 0; stripSpaceTagsCheckBox = 0; addExtraTagsCheckBox = 0; originalCheckBox = 0; resizeCheckBox = 0; dimensionSpinBox = 0; imageQualitySpinBox = 0; extendedPublicationButton = 0; extendedTagsButton = 0; contentTypeComboBox = 0; safetyLevelComboBox = 0; userNameDisplayLabel = 0; authProgressDlg = 0; tagsLineEdit = 0; widget = 0; talker = 0; imglst = 0; select = 0; albumDlg = 0; iface = 0; } unsigned int uploadCount; unsigned int uploadTotal; QString serviceName; QPushButton* newAlbumBtn; QPushButton* changeUserButton; QPushButton* removeAccount; QComboBox* albumsListComboBox; QCheckBox* publicCheckBox; QCheckBox* familyCheckBox; QCheckBox* friendsCheckBox; QCheckBox* exportHostTagsCheckBox; QCheckBox* stripSpaceTagsCheckBox; QCheckBox* addExtraTagsCheckBox; QCheckBox* originalCheckBox; QCheckBox* resizeCheckBox; QSpinBox* dimensionSpinBox; QSpinBox* imageQualitySpinBox; QPushButton* extendedPublicationButton; QPushButton* extendedTagsButton; WSComboBoxIntermediate* contentTypeComboBox; WSComboBoxIntermediate* safetyLevelComboBox; QString username; QString userId; QString lastSelectedAlbum; QLabel* userNameDisplayLabel; QProgressDialog* authProgressDlg; QList< QPair > uploadQueue; QLineEdit* tagsLineEdit; FlickrWidget* widget; FlickrTalker* talker; FlickrList* imglst; WSSelectUserDlg* select; FlickrNewAlbumDlg* albumDlg; DInfoInterface* iface; }; FlickrWindow::FlickrWindow(DInfoInterface* const iface, QWidget* const /*parent*/, const QString& serviceName) : WSToolDialog(0), d(new Private) { d->iface = iface; d->serviceName = serviceName; setWindowTitle(i18n("Export to %1 Web Service", d->serviceName)); setModal(false); KConfig config; KConfigGroup grp = config.group(QString::fromLatin1("%1Export Settings").arg(d->serviceName)); if (grp.exists()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << QString::fromLatin1("%1Export Settings").arg(d->serviceName) << " exists, deleting it"; grp.deleteGroup(); } d->select = new WSSelectUserDlg(0, serviceName); d->uploadCount = 0; d->uploadTotal = 0; d->widget = new FlickrWidget(this, iface, serviceName); d->albumDlg = new FlickrNewAlbumDlg(this, QString::fromLatin1("Flickr")); d->albumsListComboBox = d->widget->getAlbumsCoB(); d->newAlbumBtn = d->widget->getNewAlbmBtn(); d->originalCheckBox = d->widget->getOriginalCheckBox(); d->resizeCheckBox = d->widget->getResizeCheckBox(); d->publicCheckBox = d->widget->d->publicCheckBox; d->familyCheckBox = d->widget->d->familyCheckBox; d->friendsCheckBox = d->widget->d->friendsCheckBox; d->dimensionSpinBox = d->widget->getDimensionSpB(); d->imageQualitySpinBox = d->widget->getImgQualitySpB(); d->extendedTagsButton = d->widget->d->extendedTagsButton; d->addExtraTagsCheckBox = d->widget->d->addExtraTagsCheckBox; d->extendedPublicationButton = d->widget->d->extendedPublicationButton; d->safetyLevelComboBox = d->widget->d->safetyLevelComboBox; d->contentTypeComboBox = d->widget->d->contentTypeComboBox; d->tagsLineEdit = d->widget->d->tagsLineEdit; d->exportHostTagsCheckBox = d->widget->d->exportHostTagsCheckBox; d->stripSpaceTagsCheckBox = d->widget->d->stripSpaceTagsCheckBox; d->changeUserButton = d->widget->getChangeUserBtn(); d->removeAccount = d->widget->d->removeAccount; d->userNameDisplayLabel = d->widget->getUserNameLabel(); d->imglst = d->widget->d->imglst; startButton()->setText(i18n("Start Uploading")); startButton()->setToolTip(QString()); setMainWidget(d->widget); d->widget->setMinimumSize(800, 600); connect(d->imglst, SIGNAL(signalImageListChanged()), this, SLOT(slotImageListChanged())); // -------------------------------------------------------------------------- d->talker = new FlickrTalker(this, serviceName, d->iface); connect(d->talker, SIGNAL(signalError(QString)), d->talker, SLOT(slotError(QString))); connect(d->talker, SIGNAL(signalBusy(bool)), this, SLOT(slotBusy(bool))); connect(d->talker, SIGNAL(signalAddPhotoSucceeded()), this, SLOT(slotAddPhotoSucceeded())); connect(d->talker, SIGNAL(signalAddPhotoFailed(QString)), this, SLOT(slotAddPhotoFailed(QString))); connect(d->talker, SIGNAL(signalAddPhotoSetSucceeded()), this, SLOT(slotAddPhotoSetSucceeded())); connect(d->talker, SIGNAL(signalListPhotoSetsSucceeded()), this, SLOT(slotPopulatePhotoSetComboBox())); connect(d->talker, SIGNAL(signalListPhotoSetsFailed(QString)), this, SLOT(slotListPhotoSetsFailed(QString))); connect(d->talker, SIGNAL(signalLinkingSucceeded()), this, SLOT(slotLinkingSucceeded())); connect(d->widget->progressBar(), SIGNAL(signalProgressCanceled()), this, SLOT(slotAddPhotoCancelAndClose())); connect(d->widget->getReloadBtn(), SIGNAL(clicked()), this, SLOT(slotReloadPhotoSetRequest())); // -------------------------------------------------------------------------- connect(d->changeUserButton, SIGNAL(clicked()), this, SLOT(slotUserChangeRequest())); connect(d->removeAccount, SIGNAL(clicked()), this, SLOT(slotRemoveAccount())); connect(d->newAlbumBtn, SIGNAL(clicked()), this, SLOT(slotCreateNewPhotoSet())); // -------------------------------------------------------------------------- d->authProgressDlg = new QProgressDialog(this); d->authProgressDlg->setModal(true); d->authProgressDlg->setAutoReset(true); d->authProgressDlg->setAutoClose(true); d->authProgressDlg->setMaximum(0); d->authProgressDlg->reset(); connect(d->authProgressDlg, SIGNAL(canceled()), this, SLOT(slotAuthCancel())); d->talker->m_authProgressDlg = d->authProgressDlg; // -------------------------------------------------------------------------- connect(this, &QDialog::finished, this, &FlickrWindow::slotFinished); connect(this, SIGNAL(cancelClicked()), this, SLOT(slotCancelClicked())); connect(startButton(), &QPushButton::clicked, this, &FlickrWindow::slotUser1); d->select->reactivate(); readSettings(d->select->getUserName()); d->talker->link(d->select->getUserName()); } FlickrWindow::~FlickrWindow() { delete d->select; delete d->authProgressDlg; delete d->talker; delete d->widget; delete d; } void FlickrWindow::setItemsList(const QList& urls) { d->widget->imagesList()->slotAddImages(urls); } void FlickrWindow::closeEvent(QCloseEvent* e) { if (!e) { return; } slotFinished(); e->accept(); } void FlickrWindow::slotFinished() { writeSettings(); d->imglst->listView()->clear(); } void FlickrWindow::setUiInProgressState(bool inProgress) { setRejectButtonMode(inProgress ? QDialogButtonBox::Cancel : QDialogButtonBox::Close); if (inProgress) { d->widget->progressBar()->show(); } else { d->widget->progressBar()->hide(); d->widget->progressBar()->progressCompleted(); } } void FlickrWindow::slotCancelClicked() { d->talker->cancel(); d->uploadQueue.clear(); setUiInProgressState(false); } void FlickrWindow::slotAddPhotoCancelAndClose() { writeSettings(); d->imglst->listView()->clear(); d->uploadQueue.clear(); d->widget->progressBar()->reset(); setUiInProgressState(false); d->talker->cancel(); reject(); } void FlickrWindow::reactivate() { d->userNameDisplayLabel->setText(QString()); readSettings(d->select->getUserName()); d->talker->link(d->select->getUserName()); d->widget->d->imglst->loadImagesFromCurrentSelection(); show(); } void FlickrWindow::readSettings(QString uname) { KConfig config; - qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Group name is : "<serviceName, uname); - KConfigGroup grp = config.group(QString::fromLatin1("%1%2Export Settings").arg(d->serviceName, uname)); - d->exportHostTagsCheckBox->setChecked(grp.readEntry("Export Host Tags", false)); - d->extendedTagsButton->setChecked(grp.readEntry("Show Extended Tag Options", false)); - d->addExtraTagsCheckBox->setChecked(grp.readEntry("Add Extra Tags", false)); - d->stripSpaceTagsCheckBox->setChecked(grp.readEntry("Strip Space From Tags", false)); - d->stripSpaceTagsCheckBox->setEnabled(d->exportHostTagsCheckBox->isChecked()); - d->exportHostTagsCheckBox->setEnabled(false); - d->stripSpaceTagsCheckBox->setEnabled(false); + QString groupName = QString::fromLatin1("%1%2Export Settings").arg(d->serviceName, uname); + qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Group name is:" << groupName; + KConfigGroup grp = config.group(groupName); + + d->exportHostTagsCheckBox->setChecked(grp.readEntry("Export Host Tags", false)); + d->extendedTagsButton->setChecked(grp.readEntry("Show Extended Tag Options", false)); + d->addExtraTagsCheckBox->setChecked(grp.readEntry("Add Extra Tags", false)); + d->stripSpaceTagsCheckBox->setChecked(grp.readEntry("Strip Space From Tags", false)); d->publicCheckBox->setChecked(grp.readEntry("Public Sharing", false)); d->familyCheckBox->setChecked(grp.readEntry("Family Sharing", false)); d->friendsCheckBox->setChecked(grp.readEntry("Friends Sharing", false)); d->extendedPublicationButton->setChecked(grp.readEntry("Show Extended Publication Options", false)); int safetyLevel = d->safetyLevelComboBox->findData(QVariant(grp.readEntry("Safety Level", 0))); if (safetyLevel == -1) { safetyLevel = 0; } d->safetyLevelComboBox->setCurrentIndex(safetyLevel); int contentType = d->contentTypeComboBox->findData(QVariant(grp.readEntry("Content Type", 0))); if (contentType == -1) { contentType = 0; } d->contentTypeComboBox->setCurrentIndex(contentType); d->originalCheckBox->setChecked(grp.readEntry("Upload Original", false)); d->resizeCheckBox->setChecked(grp.readEntry("Resize", false)); d->dimensionSpinBox->setValue(grp.readEntry("Maximum Width", 1600)); d->imageQualitySpinBox->setValue(grp.readEntry("Image Quality", 85)); winId(); KConfigGroup dialogGroup = config.group(QString::fromLatin1("%1Export Dialog").arg(d->serviceName)); KWindowConfig::restoreWindowSize(windowHandle(), dialogGroup); resize(windowHandle()->size()); } void FlickrWindow::writeSettings() { KConfig config; - qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Group name is : "<serviceName,d->username); + QString groupName = QString::fromLatin1("%1%2Export Settings").arg(d->serviceName, d->username); + qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Group name is:" << groupName; - if (QString::compare(QString::fromLatin1("%1Export Settings").arg(d->serviceName), - QString::fromLatin1("%1%2Export Settings").arg(d->serviceName, d->username), Qt::CaseInsensitive) == 0) + if (QString::compare(QString::fromLatin1("%1Export Settings").arg(d->serviceName), groupName) == 0) { - qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Not writing entry of group " << QString::fromLatin1("%1%2Export Settings").arg(d->serviceName,d->username); + qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Not writing entry of group" << groupName; return; } - KConfigGroup grp = config.group(QString::fromLatin1("%1%2Export Settings").arg(d->serviceName, d->username)); + KConfigGroup grp = config.group(groupName); + grp.writeEntry("username", d->username); grp.writeEntry("Export Host Tags", d->exportHostTagsCheckBox->isChecked()); grp.writeEntry("Show Extended Tag Options", d->extendedTagsButton->isChecked()); grp.writeEntry("Add Extra Tags", d->addExtraTagsCheckBox->isChecked()); grp.writeEntry("Strip Space From Tags", d->stripSpaceTagsCheckBox->isChecked()); grp.writeEntry("Public Sharing", d->publicCheckBox->isChecked()); grp.writeEntry("Family Sharing", d->familyCheckBox->isChecked()); grp.writeEntry("Friends Sharing", d->friendsCheckBox->isChecked()); grp.writeEntry("Show Extended Publication Options", d->extendedPublicationButton->isChecked()); int safetyLevel = d->safetyLevelComboBox->itemData(d->safetyLevelComboBox->currentIndex()).toInt(); grp.writeEntry("Safety Level", safetyLevel); int contentType = d->contentTypeComboBox->itemData(d->contentTypeComboBox->currentIndex()).toInt(); grp.writeEntry("Content Type", contentType); grp.writeEntry("Resize", d->resizeCheckBox->isChecked()); grp.writeEntry("Upload Original", d->originalCheckBox->isChecked()); grp.writeEntry("Maximum Width", d->dimensionSpinBox->value()); grp.writeEntry("Image Quality", d->imageQualitySpinBox->value()); KConfigGroup dialogGroup = config.group(QString::fromLatin1("%1Export Dialog").arg(d->serviceName)); KWindowConfig::saveWindowSize(windowHandle(), dialogGroup); config.sync(); } void FlickrWindow::slotLinkingSucceeded() { d->username = d->talker->getUserName(); d->userId = d->talker->getUserId(); - qCDebug(DIGIKAM_WEBSERVICES_LOG) << "SlotLinkingSucceeded invoked setting user Display name to " << d->username; + qCDebug(DIGIKAM_WEBSERVICES_LOG) << "SlotLinkingSucceeded invoked setting user Display name to" << d->username; d->userNameDisplayLabel->setText(QString::fromLatin1("%1").arg(d->username)); KConfig config; foreach(const QString& group, config.groupList()) { if (!(group.contains(d->serviceName))) continue; KConfigGroup grp = config.group(group); if (group.contains(d->username)) { readSettings(d->username); break; } } writeSettings(); d->talker->listPhotoSets(); } void FlickrWindow::slotBusy(bool val) { if (val) { setCursor(Qt::WaitCursor); } else { setCursor(Qt::ArrowCursor); } } void FlickrWindow::slotError(const QString& msg) { QMessageBox::critical(this, i18n("Error"), msg); } void FlickrWindow::slotUserChangeRequest() { writeSettings(); d->userNameDisplayLabel->setText(QString()); - qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Slot Change User Request "; + qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Slot Change User Request"; d->select->reactivate(); readSettings(d->select->getUserName()); d->talker->link(d->select->getUserName()); } void FlickrWindow::slotRemoveAccount() { KConfig config; - KConfigGroup grp = config.group(QString::fromLatin1("%1%2Export Settings").arg(d->serviceName).arg(d->username)); + QString groupName = QString::fromLatin1("%1%2Export Settings").arg(d->serviceName, d->username); + KConfigGroup grp = config.group(groupName); if (grp.exists()) { - qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Removing Account having group"<serviceName); + qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Removing Account having group" << groupName; grp.deleteGroup(); } d->talker->unLink(); d->talker->removeUserName(d->serviceName + d->username); d->userNameDisplayLabel->setText(QString()); d->username = QString(); } /** * Try to guess a sensible set name from the urls given. * Currently, it extracs the last path name component, and returns the most * frequently seen. The function could be expanded to, for example, only * accept the path if it occurs at least 50% of the time. It could also look * further up in the path name. */ QString FlickrWindow::guessSensibleSetName(const QList& urlList) const { - QMap nrFolderOccurences; + QMap nrFolderOccurences; // Extract last component of directory foreach(const QUrl& url, urlList) { QString dir = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile(); QStringList list = dir.split(QLatin1Char('/')); if (list.isEmpty()) continue; nrFolderOccurences[list.last()]++; } int maxCount = 0; int totalCount = 0; QString name; - for (QMap::const_iterator it = nrFolderOccurences.constBegin(); + for (QMap::const_iterator it = nrFolderOccurences.constBegin(); it != nrFolderOccurences.constEnd() ; ++it) { totalCount += it.value(); if (it.value() > maxCount) { maxCount = it.value(); name = it.key(); } } // If there is only one entry or one name appears at least twice, return the suggestion if (totalCount == 1 || maxCount > 1) return name; return QString(); } /** This method is called when the photo set creation button is pressed. It * summons a creation dialog for user input. When that is closed, it * creates a new photo set in the local list. The id gets the form of * UNDEFINED_ followed by a number, to indicate that it doesn't exist on * Flickr yet. */ void FlickrWindow::slotCreateNewPhotoSet() { if (d->albumDlg->exec() == QDialog::Accepted) { FPhotoSet fps; d->albumDlg->getFolderProperties(fps); - qCDebug(DIGIKAM_WEBSERVICES_LOG) << "in slotCreateNewPhotoSet() " << fps.title; + qCDebug(DIGIKAM_WEBSERVICES_LOG) << "in slotCreateNewPhotoSet()" << fps.title; // Lets find an UNDEFINED_ style id that isn't taken yet.s QString id; int i = 0; id = QString::fromLatin1("UNDEFINED_") + QString::number(i); QLinkedList::iterator it = d->talker->m_photoSetsList->begin(); while (it != d->talker->m_photoSetsList->end()) { FPhotoSet fps = *it; if (fps.id == id) { id = QString::fromLatin1("UNDEFINED_") + QString::number(++i); it = d->talker->m_photoSetsList->begin(); } ++it; } fps.id = id; - qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Created new photoset with temporary id " << id; + qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Created new photoset with temporary id" << id; // Append the new photoset to the list. d->talker->m_photoSetsList->prepend(fps); d->talker->m_selectedPhotoSet = fps; // Re-populate the photo sets combo box. slotPopulatePhotoSetComboBox(); } else { - qCDebug(DIGIKAM_WEBSERVICES_LOG) << "New Photoset creation aborted "; + qCDebug(DIGIKAM_WEBSERVICES_LOG) << "New Photoset creation aborted"; } } void FlickrWindow::slotAuthCancel() { d->talker->cancel(); d->authProgressDlg->hide(); } void FlickrWindow::slotPopulatePhotoSetComboBox() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "slotPopulatePhotoSetComboBox invoked"; if (d->talker && d->talker->m_photoSetsList) { QLinkedList * const list = d->talker->m_photoSetsList; d->albumsListComboBox->clear(); d->albumsListComboBox->insertItem(0, i18n("Photostream Only")); d->albumsListComboBox->insertSeparator(1); QLinkedList::iterator it = list->begin(); int index = 2; int curr_index = 0; while (it != list->end()) { FPhotoSet photoSet = *it; QString name = photoSet.title; // Store the id as user data, because the title is not unique. QVariant id = QVariant(photoSet.id); if (id == d->talker->m_selectedPhotoSet.id) { curr_index = index; } d->albumsListComboBox->insertItem(index++, name, id); ++it; } d->albumsListComboBox->setCurrentIndex(curr_index); } } /** This slot is call when 'Start Uploading' button is pressed. */ void FlickrWindow::slotUser1() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "SlotUploadImages invoked"; //d->widget->d->tab->setCurrentIndex(FlickrWidget::FILELIST); if (d->imglst->imageUrls().isEmpty()) { return; } typedef QPair Pair; d->uploadQueue.clear(); - for (int i = 0; i < d->imglst->listView()->topLevelItemCount(); ++i) + for (int i = 0 ; i < d->imglst->listView()->topLevelItemCount() ; ++i) { FlickrListViewItem* const lvItem = dynamic_cast(d->imglst->listView()->topLevelItem(i)); if (lvItem) { DItemInfo info(d->iface->itemInfo(lvItem->url())); - qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Adding images"<< lvItem->url() << " to the list"; + qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Adding images" << lvItem->url() << " to the list"; FPhotoInfo temp; temp.title = info.title(); temp.description = info.comment(); temp.size = info.fileSize(); temp.is_public = lvItem->isPublic() ? 1 : 0; temp.is_family = lvItem->isFamily() ? 1 : 0; temp.is_friend = lvItem->isFriends() ? 1 : 0; temp.safety_level = lvItem->safetyLevel(); temp.content_type = lvItem->contentType(); QStringList tagsFromDialog = d->tagsLineEdit->text().split(QLatin1Char(','), QString::SkipEmptyParts); QStringList tagsFromList = lvItem->extraTags(); QStringList allTags; QStringList::Iterator itTags; // Tags from the dialog itTags = tagsFromDialog.begin(); while (itTags != tagsFromDialog.end()) { allTags.append(*itTags); ++itTags; } // Tags from the database if (d->exportHostTagsCheckBox->isChecked()) { QStringList tagsFromDatabase; tagsFromDatabase = info.keywords(); itTags = tagsFromDatabase.begin(); while (itTags != tagsFromDatabase.end()) { allTags.append(*itTags); ++itTags; } } // Tags from the list view. itTags = tagsFromList.begin(); while (itTags != tagsFromList.end()) { allTags.append(*itTags); ++itTags; } // Remove spaces if the user doesn't like them. if (d->stripSpaceTagsCheckBox->isChecked()) { for (QStringList::iterator it = allTags.begin(); it != allTags.end(); ++it) { *it = (*it).trimmed().remove(QLatin1Char(' ')); } } // Debug the tag list. itTags = allTags.begin(); while (itTags != allTags.end()) { - qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Tags list: " << (*itTags); + qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Tags list:" << (*itTags); ++itTags; } temp.tags = allTags; d->uploadQueue.append(Pair(lvItem->url(), temp)); } } d->uploadTotal = d->uploadQueue.count(); d->uploadCount = 0; d->widget->progressBar()->reset(); slotAddPhotoNext(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "SlotUploadImages done"; } void FlickrWindow::slotAddPhotoNext() { if (d->uploadQueue.isEmpty()) { d->widget->progressBar()->reset(); setUiInProgressState(false); return; } typedef QPair Pair; Pair pathComments = d->uploadQueue.first(); FPhotoInfo info = pathComments.second; QString selectedPhotoSetId = d->albumsListComboBox->itemData(d->albumsListComboBox->currentIndex()).toString(); if (selectedPhotoSetId.isEmpty()) { d->talker->m_selectedPhotoSet = FPhotoSet(); } else { QLinkedList::iterator it = d->talker->m_photoSetsList->begin(); while (it != d->talker->m_photoSetsList->end()) { if (it->id == selectedPhotoSetId) { d->talker->m_selectedPhotoSet = *it; break; } ++it; } } - qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Max allowed file size is : "<<((d->talker->getMaxAllowedFileSize()).toLongLong())<<"File Size is "<talker->getMaxAllowedFileSize().toLongLong() + << "File Size is" << info.size; bool res = d->talker->addPhoto(pathComments.first.toLocalFile(), //the file path - info, - d->originalCheckBox->isChecked(), - d->resizeCheckBox->isChecked(), - d->dimensionSpinBox->value(), - d->imageQualitySpinBox->value()); + info, + d->originalCheckBox->isChecked(), + d->resizeCheckBox->isChecked(), + d->dimensionSpinBox->value(), + d->imageQualitySpinBox->value()); if (!res) { slotAddPhotoFailed(QString::fromLatin1("")); return; } if (d->widget->progressBar()->isHidden()) { setUiInProgressState(true); d->widget->progressBar()->progressScheduled(i18n("Flickr Export"), true, true); d->widget->progressBar()->progressThumbnailChanged(QIcon(QLatin1String("flickr")).pixmap(22, 22)); } } void FlickrWindow::slotAddPhotoSucceeded() { // Remove photo uploaded from the list d->imglst->removeItemByUrl(d->uploadQueue.first().first); d->uploadQueue.removeFirst(); d->uploadCount++; d->widget->progressBar()->setMaximum(d->uploadTotal); d->widget->progressBar()->setValue(d->uploadCount); slotAddPhotoNext(); } void FlickrWindow::slotListPhotoSetsFailed(const QString& msg) { - QMessageBox::critical(this, QString::fromLatin1("Error"), i18n("Failed to Fetch Photoset information from %1. %2\n", d->serviceName, msg)); + QMessageBox::critical(this, QString::fromLatin1("Error"), + i18n("Failed to Fetch Photoset information from %1. %2\n", + d->serviceName, msg)); } void FlickrWindow::slotAddPhotoFailed(const QString& msg) { QPointer warn = new QMessageBox(QMessageBox::Warning, i18n("Warning"), - i18n("Failed to upload photo into %1. %2\nDo you want to continue?", d->serviceName, msg), + i18n("Failed to upload photo into %1. %2\nDo you want to continue?", + d->serviceName, msg), QMessageBox::Yes | QMessageBox::No); (warn->button(QMessageBox::Yes))->setText(i18n("Continue")); (warn->button(QMessageBox::No))->setText(i18n("Cancel")); if (warn->exec() != QMessageBox::Yes) { d->uploadQueue.clear(); d->widget->progressBar()->reset(); setUiInProgressState(false); } else { d->uploadQueue.removeFirst(); d->uploadTotal--; d->widget->progressBar()->setMaximum(d->uploadTotal); d->widget->progressBar()->setValue(d->uploadCount); slotAddPhotoNext(); } delete warn; } /* Method called when a photo set has been successfully created on Flickr. * It functions to restart the normal flow after a photo set has been created * on Flickr. */ void FlickrWindow::slotAddPhotoSetSucceeded() { slotPopulatePhotoSetComboBox(); slotAddPhotoSucceeded(); } void FlickrWindow::slotImageListChanged() { startButton()->setEnabled(!(d->widget->d->imglst->imageUrls().isEmpty())); } void FlickrWindow::slotReloadPhotoSetRequest() { d->talker->listPhotoSets(); } } // namespace Digikam diff --git a/core/utilities/import/backend/umscamera.cpp b/core/utilities/import/backend/umscamera.cpp index 9b9b7e28be..320f982658 100644 --- a/core/utilities/import/backend/umscamera.cpp +++ b/core/utilities/import/backend/umscamera.cpp @@ -1,693 +1,693 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2004-12-21 * Description : USB Mass Storage camera interface * * Copyright (C) 2004-2005 by Renchi Raju * Copyright (C) 2005-2018 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 "umscamera.h" // C ANSI includes extern "C" { #include #include #include #include } // Qt includes #include #include #include #include #include #include #include #include #include // KDE includes #include #include #include #include #include #include // Local includes #include "drawdecoder.h" #include "digikam_debug.h" #include "digikam_config.h" #include "dimg.h" #include "dmetadata.h" #include "imagescanner.h" namespace Digikam { UMSCamera::UMSCamera(const QString& title, const QString& model, const QString& port, const QString& path) : DKCamera(title, model, port, path) { m_cancel = false; getUUIDFromSolid(); } UMSCamera::~UMSCamera() { } // Method not supported by UMS camera. bool UMSCamera::getPreview(QImage& /*preview*/) { return false; } // Method not supported by UMS camera. bool UMSCamera::capture(CamItemInfo& /*itemInfo*/) { return false; } DKCamera::CameraDriverType UMSCamera::cameraDriverType() { return DKCamera::UMSDriver; } QByteArray UMSCamera::cameraMD5ID() { QString camData; // Camera media UUID is used (if available) to improve fingerprint value // registration to database. We want to be sure that MD5 sum is really unique. // We don't use camera information here. media UUID is enough because it came be // mounted by a card reader or a camera. In this case, "already downloaded" flag will // be independent of the device used to mount memory card. camData.append(uuid()); QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(camData.toUtf8()); return md5.result().toHex(); } bool UMSCamera::getFreeSpace(unsigned long& /*kBSize*/, unsigned long& /*kBAvail*/) { return false; // NOTE: implemented in gui, outside the camera thread. } bool UMSCamera::doConnect() { QFileInfo dir(m_path); if (!dir.exists() || !dir.isReadable() || !dir.isDir()) { return false; } if (dir.isWritable()) { m_deleteSupport = true; m_uploadSupport = true; m_mkDirSupport = true; m_delDirSupport = true; } else { m_deleteSupport = false; m_uploadSupport = false; m_mkDirSupport = false; m_delDirSupport = false; } m_thumbnailSupport = true; // UMS camera always support thumbnails. m_captureImageSupport = false; // UMS camera never support capture mode. return true; } void UMSCamera::cancel() { // set the cancel flag m_cancel = true; } bool UMSCamera::getFolders(const QString& folder) { if (m_cancel) { return false; } QDir dir(folder); dir.setFilter(QDir::Dirs | QDir::Executable); const QFileInfoList list = dir.entryInfoList(); if (list.isEmpty()) { return true; } QFileInfoList::const_iterator fi; QStringList subFolderList; for (fi = list.constBegin() ; !m_cancel && (fi != list.constEnd()) ; ++fi) { if (fi->fileName() == QLatin1String(".") || fi->fileName() == QLatin1String("..")) { continue; } QString subFolder = folder + QString(folder.endsWith(QLatin1Char('/')) ? QLatin1String("") : QLatin1String("/")) + fi->fileName(); subFolderList.append(subFolder); } if (subFolderList.isEmpty()) { return true; } emit signalFolderList(subFolderList); return true; } bool UMSCamera::getItemsInfoList(const QString& folder, bool useMetadata, CamItemInfoList& infoList) { m_cancel = false; infoList.clear(); QDir dir(folder); dir.setFilter(QDir::Files); if (!dir.exists()) { return false; } const QFileInfoList list = dir.entryInfoList(); if (list.isEmpty()) { return true; // Nothing to do. } for (QFileInfoList::const_iterator fi = list.constBegin() ; !m_cancel && (fi != list.constEnd()) ; ++fi) { CamItemInfo info; getItemInfo(folder, fi->fileName(), info, useMetadata); infoList.append(info); } return true; } void UMSCamera::getItemInfo(const QString& folder, const QString& itemName, CamItemInfo& info, bool useMetadata) { info.folder = !folder.endsWith(QLatin1Char('/')) ? folder + QLatin1Char('/') : folder; info.name = itemName; QFileInfo fi(info.folder + info.name); info.size = fi.size(); info.readPermissions = fi.isReadable(); info.writePermissions = fi.isWritable(); info.mime = mimeType(fi.suffix().toLower()); if (!info.mime.isEmpty()) { if (useMetadata) { // Try to use file metadata DMetadata meta; getMetadata(folder, itemName, meta); fillItemInfoFromMetadata(info, meta); // Fall back to file system info if (info.ctime.isNull()) { info.ctime = ImageScanner::creationDateFromFilesystem(fi); } } else { // Only use file system date info.ctime = ImageScanner::creationDateFromFilesystem(fi); } } // if we have an image, allow previews // TODO allow video previews at some point? /* if (info.mime.startsWith(QLatin1String("image/")) || info.mime.startsWith(QLatin1String("video/"))) */ if (info.mime.startsWith(QLatin1String("image/"))) { info.previewPossible = true; } } bool UMSCamera::getThumbnail(const QString& folder, const QString& itemName, QImage& thumbnail) { m_cancel = false; - QString path = folder + QLatin1String("/") + itemName; + QString path = folder + QLatin1Char('/') + itemName; // Try to get preview from Exif data (good quality). Can work with Raw files DMetadata metadata(path); metadata.getImagePreview(thumbnail); if (!thumbnail.isNull()) { return true; } // RAW files : try to extract embedded thumbnail using RawEngine DRawDecoder::loadRawPreview(thumbnail, path); if (!thumbnail.isNull()) { return true; } KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(QLatin1String("Camera Settings")); bool turnHighQualityThumbs = group.readEntry(QLatin1String("TurnHighQualityThumbs"), false); // Try to get thumbnail from Exif data (poor quality). if (!turnHighQualityThumbs) { thumbnail = metadata.getExifThumbnail(true); if (!thumbnail.isNull()) { return true; } } // THM files: try to get thumbnail from '.thm' files if we didn't manage to get // thumbnail from Exif. Any cameras provides *.thm files like JPEG files with RAW files. // Using this way is always speed up than ultimate loading using DImg. // Note: the thumbnail extracted with this method can be in poor quality. // 2006/27/01 - Gilles - Tested with my Minolta Dynax 5D USM camera. QFileInfo fi(path); - if (thumbnail.load(folder + QLatin1String("/") + fi.baseName() + QLatin1String(".thm"))) // Lowercase + if (thumbnail.load(folder + QLatin1Char('/') + fi.baseName() + QLatin1String(".thm"))) // Lowercase { if (!thumbnail.isNull()) { return true; } } - else if (thumbnail.load(folder + QLatin1String("/") + fi.baseName() + QLatin1String(".THM"))) // Uppercase + else if (thumbnail.load(folder + QLatin1Char('/') + fi.baseName() + QLatin1String(".THM"))) // Uppercase { if (!thumbnail.isNull()) { return true; } } // Finally, we trying to get thumbnail using DImg API (slow). qCDebug(DIGIKAM_IMPORTUI_LOG) << "Use DImg loader to get thumbnail from : " << path; DImg dimgThumb; // skip loading the data we don't need to speed it up. dimgThumb.load(path, false /*loadMetadata*/, false /*loadICCData*/, false /*loadUniqueHash*/, false /*loadHistory*/); if (!dimgThumb.isNull()) { thumbnail = dimgThumb.copyQImage(); return true; } return false; } bool UMSCamera::getMetadata(const QString& folder, const QString& itemName, DMetadata& meta) { QFileInfo fi, thmlo, thmup; bool ret = false; - fi.setFile(folder + QLatin1String("/") + itemName); - thmlo.setFile(folder + QLatin1String("/") + fi.baseName() + QLatin1String(".thm")); - thmup.setFile(folder + QLatin1String("/") + fi.baseName() + QLatin1String(".THM")); + fi.setFile(folder + QLatin1Char('/') + itemName); + thmlo.setFile(folder + QLatin1Char('/') + fi.baseName() + QLatin1String(".thm")); + thmup.setFile(folder + QLatin1Char('/') + fi.baseName() + QLatin1String(".THM")); if (thmlo.exists()) { // Try thumbnail sidecar files with lowercase extension. ret = meta.load(thmlo.filePath()); } else if (thmup.exists()) { // Try thumbnail sidecar files with uppercase extension. ret = meta.load(thmup.filePath()); } else { // If no thumbnail sidecar file available, try to load image metadata for files. ret = meta.load(fi.filePath()); } return ret; } bool UMSCamera::downloadItem(const QString& folder, const QString& itemName, const QString& saveFile) { m_cancel = false; - QString src = folder + QLatin1String("/") + itemName; + QString src = folder + QLatin1Char('/') + itemName; QString dest = saveFile; QFile sFile(src); QFile dFile(dest); if (!sFile.open(QIODevice::ReadOnly)) { qCWarning(DIGIKAM_IMPORTUI_LOG) << "Failed to open source file for reading: " << src; return false; } if (!dFile.open(QIODevice::WriteOnly)) { sFile.close(); qCWarning(DIGIKAM_IMPORTUI_LOG) << "Failed to open destination file for writing: " << dest; return false; } const int MAX_IPC_SIZE = (1024 * 32); char buffer[MAX_IPC_SIZE]; qint64 len; while (((len = sFile.read(buffer, MAX_IPC_SIZE)) != 0) && !m_cancel) { if ((len == -1) || (dFile.write(buffer, (quint64)len) != len)) { sFile.close(); dFile.close(); return false; } } sFile.close(); dFile.close(); // Set the file modification time of the downloaded file to the original file. // NOTE: this behavior don't need to be managed through Setup/Metadata settings. QT_STATBUF st; if (QT_STAT(QFile::encodeName(src).constData(), &st) == 0) { struct utimbuf ut; ut.modtime = st.st_mtime; ut.actime = st.st_atime; ::utime(QFile::encodeName(dest).constData(), &ut); } return true; } bool UMSCamera::setLockItem(const QString& folder, const QString& itemName, bool lock) { - QString src = folder + QLatin1String("/") + itemName; + QString src = folder + QLatin1Char('/') + itemName; if (lock) { // Lock the file to set read only flag if (::chmod(QFile::encodeName(src).constData(), S_IREAD) == -1) { return false; } } else { // Unlock the file to set read/write flag if (::chmod(QFile::encodeName(src).constData(), S_IREAD | S_IWRITE) == -1) { return false; } } return true; } bool UMSCamera::deleteItem(const QString& folder, const QString& itemName) { m_cancel = false; // Any camera provide THM (thumbnail) file with real image. We need to remove it also. - QFileInfo fi(folder + QLatin1String("/") + itemName); + QFileInfo fi(folder + QLatin1Char('/') + itemName); - QFileInfo thmLo(folder + QLatin1String("/") + fi.baseName() + QLatin1String(".thm")); // Lowercase + QFileInfo thmLo(folder + QLatin1Char('/') + fi.baseName() + QLatin1String(".thm")); // Lowercase if (thmLo.exists()) { ::unlink(QFile::encodeName(thmLo.filePath()).constData()); } - QFileInfo thmUp(folder + QLatin1String("/") + fi.baseName() + QLatin1String(".THM")); // Uppercase + QFileInfo thmUp(folder + QLatin1Char('/') + fi.baseName() + QLatin1String(".THM")); // Uppercase if (thmUp.exists()) { ::unlink(QFile::encodeName(thmUp.filePath()).constData()); } // Remove the real image. - return (::unlink(QFile::encodeName(folder + QLatin1String("/") + itemName).constData()) == 0); + return (::unlink(QFile::encodeName(folder + QLatin1Char('/') + itemName).constData()) == 0); } bool UMSCamera::uploadItem(const QString& folder, const QString& itemName, const QString& localFile, CamItemInfo& info) { m_cancel = false; - QString dest = folder + QLatin1String("/") + itemName; + QString dest = folder + QLatin1Char('/') + itemName; QString src = localFile; QFile sFile(src); QFile dFile(dest); if (!sFile.open(QIODevice::ReadOnly)) { qCWarning(DIGIKAM_IMPORTUI_LOG) << "Failed to open source file for reading: " << src; return false; } if (!dFile.open(QIODevice::WriteOnly)) { sFile.close(); qCWarning(DIGIKAM_IMPORTUI_LOG) << "Failed to open destination file for writing: " << dest; return false; } const int MAX_IPC_SIZE = (1024 * 32); char buffer[MAX_IPC_SIZE]; qint64 len; while (((len = sFile.read(buffer, MAX_IPC_SIZE)) != 0) && !m_cancel) { if ((len == -1) || (dFile.write(buffer, (quint64)len) == -1)) { sFile.close(); dFile.close(); return false; } } sFile.close(); dFile.close(); // Set the file modification time of the uploaded file to original file. // NOTE: this behavior don't need to be managed through Setup/Metadata settings. QT_STATBUF st; if (QT_STAT(QFile::encodeName(src).constData(), &st) == 0) { struct utimbuf ut; ut.modtime = st.st_mtime; ut.actime = st.st_atime; ::utime(QFile::encodeName(dest).constData(), &ut); } // Get new camera item information. PhotoInfoContainer pInfo; DMetadata meta; QFileInfo fi(dest); QString mime = mimeType(fi.suffix().toLower()); if (!mime.isEmpty()) { QSize dims; QDateTime dt; // Try to load image metadata. meta.load(fi.filePath()); dt = meta.getImageDateTime(); dims = meta.getImageDimensions(); pInfo = meta.getPhotographInformation(); if (dt.isNull()) // fall back to file system info { dt = ImageScanner::creationDateFromFilesystem(fi); } info.name = fi.fileName(); - info.folder = !folder.endsWith(QLatin1Char('/')) ? folder + QLatin1String("/") : folder; + info.folder = !folder.endsWith(QLatin1Char('/')) ? folder + QLatin1Char('/') : folder; info.mime = mime; info.ctime = dt; info.size = fi.size(); info.width = dims.width(); info.height = dims.height(); info.downloaded = CamItemInfo::DownloadUnknown; info.readPermissions = fi.isReadable(); info.writePermissions = fi.isWritable(); info.photoInfo = pInfo; } return true; } bool UMSCamera::cameraSummary(QString& summary) { summary = QString(i18n("Mounted Camera driver for USB/IEEE1394 mass storage cameras and " "Flash disk card readers.

")); // we do not expect title/model/etc. to contain newlines, // so we just escape HTML characters summary += i18nc("@info List of device properties", "Title: %1
" "Model: %2
" "Port: %3
" "Path: %4
" "UUID: %5

", title().toHtmlEscaped(), model().toHtmlEscaped(), port().toHtmlEscaped(), path().toHtmlEscaped(), uuid().toHtmlEscaped()); summary += i18nc("@info List of supported device operations", "Thumbnails: %1
" "Capture image: %2
" "Delete items: %3
" "Upload items: %4
" "Create directories: %5
" "Delete directories: %6

", thumbnailSupport() ? i18n("yes") : i18n("no"), captureImageSupport() ? i18n("yes") : i18n("no"), deleteSupport() ? i18n("yes") : i18n("no"), uploadSupport() ? i18n("yes") : i18n("no"), mkDirSupport() ? i18n("yes") : i18n("no"), delDirSupport() ? i18n("yes") : i18n("no")); return true; } bool UMSCamera::cameraManual(QString& manual) { manual = QString(i18n("For more information about the Mounted Camera driver, " "please read the Supported Digital Still " "Cameras section in the digiKam manual.")); return true; } bool UMSCamera::cameraAbout(QString& about) { about = QString(i18n("The Mounted Camera driver is a simple interface to a camera disk " "mounted locally on your system.

" "It does not use libgphoto2 drivers.

" "To report any problems with this driver, please contact the digiKam team at:

" "http://www.digikam.org/?q=contact")); return true; } void UMSCamera::getUUIDFromSolid() { QList devices = Solid::Device::listFromType(Solid::DeviceInterface::StorageAccess); foreach(const Solid::Device& accessDevice, devices) { // check for StorageAccess if (!accessDevice.is()) { continue; } const Solid::StorageAccess* const access = accessDevice.as(); if (!access->isAccessible()) { continue; } // check for StorageDrive Solid::Device driveDevice; for (Solid::Device currentDevice = accessDevice; currentDevice.isValid(); currentDevice = currentDevice.parent()) { if (currentDevice.is()) { driveDevice = currentDevice; break; } } if (!driveDevice.isValid()) { continue; } // check for StorageVolume Solid::Device volumeDevice; for (Solid::Device currentDevice = accessDevice; currentDevice.isValid(); currentDevice = currentDevice.parent()) { if (currentDevice.is()) { volumeDevice = currentDevice; break; } } if (!volumeDevice.isValid()) { continue; } Solid::StorageVolume* const volume = volumeDevice.as(); if (m_path.startsWith(QDir::fromNativeSeparators(access->filePath())) && QDir::fromNativeSeparators(access->filePath()) != QLatin1String("/")) { m_uuid = volume->uuid(); } } } } // namespace Digikam