1 /* Copyright (C) 2003-2006 Jesper K. Pedersen <blackie@kde.org>
3 This program is free software; you can redistribute it and/or
4 modify it under the terms of the GNU General Public
5 License as published by the Free Software Foundation; either
6 version 2 of the License, or (at your option) any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 General Public License for more details.
13 You should have received a copy of the GNU General Public License
14 along with this program; see the file COPYING. If not, write to
15 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 Boston, MA 02110-1301, USA.
18 #include "FileReader.h"
21 #include <QTextStream>
23 #include <kmessagebox.h>
24 #include <kstandarddirs.h>
28 #include "DB/MD5Map.h"
30 #include "MainWindow/Window.h"
31 #include "Utilities/Util.h"
32 #include "XMLCategory.h"
34 void XMLDB::FileReader::read( const QString
& configFile
)
36 QDomElement top
= readConfigFile( configFile
);
37 _fileVersion
= top
.attribute( QString::fromLatin1( "version" ), QString::fromLatin1( "1" ) ).toInt();
39 QDomElement categories
;
41 QDomElement blockList
;
42 QDomElement memberGroups
;
43 readTopNodeInConfigDocument( configFile
, top
, &categories
, &images
, &blockList
, &memberGroups
);
44 _db
->_members
.setLoading( true );
45 loadCategories( categories
);
47 loadBlockList( blockList
);
48 loadMemberGroups( memberGroups
);
49 _db
->_members
.setLoading( false );
51 checkIfImagesAreSorted();
52 checkIfAllImagesHasSizeAttributes();
53 checkAndWarnAboutVersionConflict();
57 void XMLDB::FileReader::readTopNodeInConfigDocument( const QString
& configFile
, QDomElement top
, QDomElement
* options
, QDomElement
* images
,
58 QDomElement
* blockList
, QDomElement
* memberGroups
)
60 for ( QDomNode node
= top
.firstChild(); !node
.isNull(); node
= node
.nextSibling() ) {
61 if ( node
.isElement() ) {
62 QDomElement elm
= node
.toElement();
63 QString tag
= elm
.tagName().toLower();
64 if ( tag
== QString::fromLatin1( "config" ) )
65 ; // Skip for compatibility with 2.1 and older
66 else if ( tag
== QString::fromLatin1( "categories" ) || tag
== QString::fromLatin1( "options" ) ) {
67 // options is for KimDaBa 2.1 compatibility
70 else if ( tag
== QString::fromLatin1( "configwindowsetup" ) )
71 ; // Skip for compatibility with 2.1 and older
72 else if ( tag
== QString::fromLatin1("images") )
74 else if ( tag
== QString::fromLatin1( "blocklist" ) )
76 else if ( tag
== QString::fromLatin1( "member-groups" ) )
79 KMessageBox::error( messageParent(),
80 i18n("Error in file %1: unexpected element: '%2'", configFile
, tag
) );
85 if ( options
->isNull() )
86 KMessageBox::sorry( messageParent(), i18n("Unable to find 'Options' tag in configuration file %1.", configFile
) );
87 if ( images
->isNull() )
88 KMessageBox::sorry( messageParent(), i18n("Unable to find 'Images' tag in configuration file %1.", configFile
) );
91 void XMLDB::FileReader::createSpecialCategories()
93 DB::CategoryPtr folderCat
= _db
->_categoryCollection
.categoryForName( QString::fromLatin1( "Folder" ) );
94 if( folderCat
.isNull() ) {
95 folderCat
= new XMLCategory( QString::fromLatin1("Folder"), QString::fromLatin1("folder"),
96 DB::Category::TreeView
, 32, false );
97 _db
->_categoryCollection
.addCategory( folderCat
);
99 folderCat
->setSpecialCategory( true );
100 dynamic_cast<XMLCategory
*>( folderCat
.data() )->setShouldSave( false );
102 DB::CategoryPtr tokenCat
= _db
->_categoryCollection
.categoryForName( QString::fromLatin1( "Tokens" ) );
104 tokenCat
= new XMLCategory( QString::fromLatin1("Tokens"), QString::fromLatin1("flag-blue"),
105 DB::Category::TreeView
, 32, true );
106 _db
->_categoryCollection
.addCategory( tokenCat
);
108 tokenCat
->setSpecialCategory( true );
110 // KPhotoAlbum 2.2 did not write the tokens to the category section, so unless we do this small trick they
111 // will not show up when importing.
112 for ( char ch
= 'A'; ch
< 'Z'; ++ch
)
113 tokenCat
->addItem( QString::fromLatin1("%1").arg( QChar::fromLatin1( ch
) ) );
115 DB::CategoryPtr mediaCat
= _db
->_categoryCollection
.categoryForName( QString::fromLatin1( "Media Type" ) );
117 mediaCat
= new XMLCategory( QString::fromLatin1("Media Type"), QString::fromLatin1("video"),
118 DB::Category::TreeView
, 32, false );
119 _db
->_categoryCollection
.addCategory( mediaCat
);
121 mediaCat
->addItem( QString::fromLatin1( "Image" ) );
122 mediaCat
->addItem( QString::fromLatin1( "Video" ) );
123 mediaCat
->setSpecialCategory( true );
124 dynamic_cast<XMLCategory
*>( mediaCat
.data() )->setShouldSave( false );
127 void XMLDB::FileReader::loadCategories( const QDomElement
& elm
)
129 // options is for KimDaBa 2.1 compatibility
130 Q_ASSERT( elm
.tagName().toLower() == QString::fromLatin1( "categories" ) || elm
.tagName().toLower() == QString::fromLatin1( "options" ) );
132 for ( QDomNode nodeOption
= elm
.firstChild(); !nodeOption
.isNull(); nodeOption
= nodeOption
.nextSibling() ) {
134 if ( nodeOption
.isElement() ) {
135 QDomElement elmOption
= nodeOption
.toElement();
136 // option is for KimDaBa 2.1 compatibility
137 Q_ASSERT( elmOption
.tagName().toLower() == QString::fromLatin1("category") ||
138 elmOption
.tagName() == QString::fromLatin1("option").toLower() );
139 QString name
= unescape( elmOption
.attribute( QString::fromLatin1("name") ) );
141 if ( !name
.isNull() ) {
142 // Read Category info
143 QString icon
= elmOption
.attribute( QString::fromLatin1("icon") );
144 DB::Category::ViewType type
=
145 (DB::Category::ViewType
) elmOption
.attribute( QString::fromLatin1("viewtype"), QString::fromLatin1( "0" ) ).toInt();
146 bool show
= (bool) elmOption
.attribute( QString::fromLatin1( "show" ),
147 QString::fromLatin1( "1" ) ).toInt();
148 int thumbnailSize
= elmOption
.attribute( QString::fromLatin1( "thumbnailsize" ), QString::fromLatin1( "32" ) ).toInt();
150 DB::CategoryPtr cat
= _db
->_categoryCollection
.categoryForName( name
);
152 cat
= new XMLCategory( name
, icon
, type
, thumbnailSize
, show
);
153 _db
->_categoryCollection
.addCategory( cat
);
157 for ( QDomNode nodeValue
= elmOption
.firstChild(); !nodeValue
.isNull();
158 nodeValue
= nodeValue
.nextSibling() ) {
159 if ( nodeValue
.isElement() ) {
160 QDomElement elmValue
= nodeValue
.toElement();
161 Q_ASSERT( elmValue
.tagName() == QString::fromLatin1("value") );
162 QString value
= elmValue
.attribute( QString::fromLatin1("value") );
163 if ( elmValue
.hasAttribute( QString::fromLatin1( "id" ) ) ) {
164 int id
= elmValue
.attribute( QString::fromLatin1( "id" ) ).toInt();
165 static_cast<XMLCategory
*>(cat
.data())->setIdMapping( value
, id
);
167 items
.append( value
);
170 cat
->setItems( items
);
175 createSpecialCategories();
178 void XMLDB::FileReader::loadImages( const QDomElement
& images
)
180 QString directory
= Settings::SettingsData::instance()->imageDirectory();
182 for ( QDomNode node
= images
.firstChild(); !node
.isNull(); node
= node
.nextSibling() ) {
184 if ( node
.isElement() )
185 elm
= node
.toElement();
189 QString fileName
= elm
.attribute( QString::fromLatin1("file") );
190 if ( fileName
.isNull() ) {
191 qWarning( "Element did not contain a file attribute" );
192 } else if (_db
->_idMapper
.exists(fileName
)) {
193 qDebug() << fileName
<< " already in database";
195 DB::ImageInfoPtr info
= load( fileName
, elm
);
196 _db
->_images
.append(info
);
197 _db
->_idMapper
.add(fileName
);
198 _db
->_md5map
.insert( info
->MD5Sum(), fileName
);
204 void XMLDB::FileReader::loadBlockList( const QDomElement
& blockList
)
206 for ( QDomNode node
= blockList
.firstChild(); !node
.isNull(); node
= node
.nextSibling() ) {
208 if ( node
.isElement() )
209 elm
= node
.toElement();
213 QString fileName
= elm
.attribute( QString::fromLatin1( "file" ) );
214 if ( !fileName
.isEmpty() )
215 _db
->_blockList
<< fileName
;
219 void XMLDB::FileReader::loadMemberGroups( const QDomElement
& memberGroups
)
221 for ( QDomNode node
= memberGroups
.firstChild(); !node
.isNull(); node
= node
.nextSibling() ) {
222 if ( node
.isElement() ) {
223 QDomElement elm
= node
.toElement();
224 QString category
= elm
.attribute( QString::fromLatin1( "category" ) );
225 if ( category
.isNull() )
226 category
= elm
.attribute( QString::fromLatin1( "option-group" ) ); // compatible with KimDaBa 2.0
228 QString group
= elm
.attribute( QString::fromLatin1( "group-name" ) );
229 if ( elm
.hasAttribute( QString::fromLatin1( "member" ) ) ) {
230 QString member
= elm
.attribute( QString::fromLatin1( "member" ) );
231 _db
->_members
.addMemberToGroup( category
, group
, member
);
234 QStringList members
= elm
.attribute( QString::fromLatin1( "members" ) ).split( QString::fromLatin1( "," ), QString::SkipEmptyParts
);
235 for( QStringList::Iterator membersIt
= members
.begin(); membersIt
!= members
.end(); ++membersIt
) {
236 DB::CategoryPtr catPtr
= _db
->_categoryCollection
.categoryForName( category
);
237 XMLCategory
* cat
= static_cast<XMLCategory
*>( catPtr
.data() );
238 QString member
= cat
->nameForId( (*membersIt
).toInt() );
239 Q_ASSERT( !member
.isNull() );
240 _db
->_members
.addMemberToGroup( category
, group
, member
);
247 void XMLDB::FileReader::checkIfImagesAreSorted()
249 if ( !KMessageBox::shouldBeShownContinue( QString::fromLatin1( "checkWhetherImagesAreSorted" ) ) )
252 QDateTime
last( QDate( 1900, 1, 1 ) );
253 bool wrongOrder
= false;
254 for( DB::ImageInfoListIterator it
= _db
->_images
.begin(); !wrongOrder
&& it
!= _db
->_images
.end(); ++it
) {
255 if ( last
> (*it
)->date().start() && (*it
)->date().start().isValid() )
257 last
= (*it
)->date().start();
261 KMessageBox::information( messageParent(),
263 i18n("<p>Your images/videos are not sorted, which means that navigating using the date bar "
264 "will only work suboptimally.</p>"
265 "<p>In the <b>Maintenance</b> menu, you can find <b>Display Images with Incomplete Dates</b> "
266 "which you can use to find the images that are missing date information.</p>"
267 "You can then select the images that you have reason to believe have a correct date "
268 "in either their EXIF data or on the file, and execute <b>Maintenance->Read EXIF Info</b> "
269 "to reread the information.</p>"
270 "<p>Finally, once all images have their dates set, you can execute "
271 "<b>Images->Sort Selected by Date & Time</b> to sort them in the database.</p>"),
273 i18n("<p>Your images/videos are not sorted, which means that navigating using the date bar "
274 "will only work suboptimally.</p>"
275 "<p>You also do not have EXIF support available, which means that you cannot read "
276 "image dates from JPEG metadata. It is strongly recommended to recompile KPhotoAlbum "
277 "with the <code>exiv2</code> library. After you have done so, you will be asked what "
278 "to do to correct all the missing information.</p>"),
280 i18n("Images/Videos Are Not Sorted"),
281 QString::fromLatin1( "checkWhetherImagesAreSorted" ) );
286 void XMLDB::FileReader::checkIfAllImagesHasSizeAttributes()
290 if ( !KMessageBox::shouldBeShownContinue( QString::fromLatin1( "checkWhetherAllImagesIncludesSize" ) ) )
293 if ( _db
->_anyImageWithEmptySize
) {
294 KMessageBox::information( messageParent(),
295 i18n("<p>Not all the images in the database have information about image sizes; this is needed to "
296 "get the best result in the thumbnail view. To fix this, simply go to the <b>Maintainance</b> menu, "
297 "and first choose <b>Remove All Thumbnails</b>, and after that choose <tt>Build Thumbnails</tt>.</p>"
298 "<p>Not doing so will result in extra space around images in the thumbnail view - that is all - so "
299 "there is no urgency in doing it.</p>"),
300 i18n("Not All Images Have Size Information"),
301 QString::fromLatin1( "checkWhetherAllImagesIncludesSize" ) );
306 void XMLDB::FileReader::checkAndWarnAboutVersionConflict()
308 if ( _fileVersion
== 1 ) {
309 KMessageBox::information( messageParent(),
310 i18n( "<p>The index.xml file read was from an older version of KPhotoAlbum. "
311 "KPhotoAlbum read the old format without problems, but to be able to convert back to "
312 "KimDaBa 2.1 format, you need to run the current KPhotoAlbum using the flag "
313 "<b>export-in-2.1-format</b>, and then save.</p>"),
314 i18n("Old File Format read"), QString::fromLatin1( "version1FileFormatRead" ) );
318 DB::ImageInfoPtr
XMLDB::FileReader::load( const QString
& fileName
, QDomElement elm
)
320 DB::ImageInfoPtr info
= XMLDB::Database::createImageInfo( fileName
, elm
, _db
);
321 _nextStackId
= qMax( _nextStackId
, info
->stackId() + 1 );
322 info
->createFolderCategoryItem( _db
->_categoryCollection
.categoryForName(QString::fromLatin1("Folder")), _db
->_members
);
326 QDomElement
XMLDB::FileReader::readConfigFile( const QString
& configFile
)
329 QFile
file( configFile
);
330 if ( !file
.exists() ) {
331 // Load a default setup
332 QFile
file(Utilities::locateDataFile(QString::fromLatin1("default-setup")));
333 if ( !file
.open( QIODevice::ReadOnly
) ) {
334 KMessageBox::information( messageParent(),
335 i18n( "<p>KPhotoAlbum was unable to load a default setup, which indicates an installation error</p>"
336 "<p>If you have installed KPhotoAlbum yourself, then you must remember to set the environment variable "
337 "<b>KDEDIRS</b>, to point to the topmost installation directory.</p>"
338 "<p>If you for example ran configure with <b>--prefix=/usr/local/kde</b>, then you must use the following "
339 "environment variable setup (this example is for Bash and compatible shells):</p>"
340 "<p><b>export KDEDIRS=/usr/local/kde</b></p>"
341 "<p>In case you already have KDEDIRS set, simply append the string as if you where setting the <b>PATH</b> "
342 "environment variable</p>"), i18n("No default setup file found") );
345 QTextStream
stream( &file
);
346 stream
.setCodec( QTextCodec::codecForName("UTF-8") );
347 QString str
= stream
.readAll();
348 str
= str
.replace( QString::fromLatin1( "People" ), i18n( "People" ) );
349 str
= str
.replace( QString::fromLatin1( "Places" ), i18n( "Places" ) );
350 str
= str
.replace( QString::fromLatin1( "Events" ), i18n( "Events" ) );
351 str
= str
.replace( QRegExp( QString::fromLatin1("imageDirectory=\"[^\"]*\"")), QString::fromLatin1("") );
352 str
= str
.replace( QRegExp( QString::fromLatin1("htmlBaseDir=\"[^\"]*\"")), QString::fromLatin1("") );
353 str
= str
.replace( QRegExp( QString::fromLatin1("htmlBaseURL=\"[^\"]*\"")), QString::fromLatin1("") );
354 doc
.setContent( str
);
358 if ( !file
.open( QIODevice::ReadOnly
) ) {
359 KMessageBox::error( messageParent(), i18n("Unable to open '%1' for reading", configFile
), i18n("Error Running Demo") );
367 if ( !doc
.setContent( &file
, false, &errMsg
, &errLine
, &errCol
)) {
368 KMessageBox::error( messageParent(), i18n("Error on line %1 column %2 in file %3: %4", errLine
, errCol
, configFile
, errMsg
) );
373 // Now read the content of the file.
374 QDomElement top
= doc
.documentElement();
375 if ( top
.isNull() ) {
376 KMessageBox::error( messageParent(), i18n("Error in file %1: No elements found", configFile
) );
380 if ( top
.tagName().toLower() != QString::fromLatin1( "kphotoalbum" ) &&
381 top
.tagName().toLower() != QString::fromLatin1( "kimdaba" ) ) { // KimDaBa compatibility
382 KMessageBox::error( messageParent(), i18n("Error in file %1: expected 'KPhotoAlbum' as top element but found '%2'", configFile
, top
.tagName() ) );
390 QString
XMLDB::FileReader::unescape( const QString
& str
)
393 tmp
.replace( QString::fromLatin1( "_" ), QString::fromLatin1( " " ) );
397 // TODO(hzeller): DEPENDENCY This pulls in the whole MainWindow dependency into the database backend.
398 QWidget
*XMLDB::FileReader::messageParent()
400 return MainWindow::Window::theMainWindow();