Krazy/EBN: changes for null strings
[kphotoalbum.git] / XMLDB / FileReader.cpp
blob5747570988fc219b4a85789861494edb09cd52fa
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"
20 #include <QTextCodec>
21 #include <QTextStream>
22 #include <klocale.h>
23 #include <kmessagebox.h>
24 #include <kstandarddirs.h>
25 #include <qfile.h>
26 #include <qregexp.h>
28 #include "DB/MD5Map.h"
29 #include "Database.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;
40 QDomElement images;
41 QDomElement blockList;
42 QDomElement memberGroups;
43 readTopNodeInConfigDocument( configFile, top, &categories, &images, &blockList, &memberGroups );
44 _db->_members.setLoading( true );
45 loadCategories( categories );
46 loadImages( images );
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
68 *options = elm;
70 else if ( tag == QString::fromLatin1( "configwindowsetup" ) )
71 ; // Skip for compatibility with 2.1 and older
72 else if ( tag == QString::fromLatin1("images") )
73 *images = elm;
74 else if ( tag == QString::fromLatin1( "blocklist" ) )
75 *blockList = elm;
76 else if ( tag == QString::fromLatin1( "member-groups" ) )
77 *memberGroups = elm;
78 else {
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" ) );
103 if ( !tokenCat ) {
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" ) );
116 if ( !mediaCat ) {
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 );
151 Q_ASSERT ( !cat );
152 cat = new XMLCategory( name, icon, type, thumbnailSize, show );
153 _db->_categoryCollection.addCategory( cat );
155 // Read values
156 QStringList items;
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() ) {
183 QDomElement elm;
184 if ( node.isElement() )
185 elm = node.toElement();
186 else
187 continue;
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";
194 } else {
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() ) {
207 QDomElement elm;
208 if ( node.isElement() )
209 elm = node.toElement();
210 else
211 continue;
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 );
233 else {
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" ) ) )
250 return;
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() )
256 wrongOrder = true;
257 last = (*it)->date().start();
260 if ( wrongOrder ) {
261 KMessageBox::information( messageParent(),
262 #ifdef HAVE_EXIV2
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>"),
272 #else
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>"),
279 #endif
280 i18n("Images/Videos Are Not Sorted"),
281 QString::fromLatin1( "checkWhetherImagesAreSorted" ) );
286 void XMLDB::FileReader::checkIfAllImagesHasSizeAttributes()
288 QTime time;
289 time.start();
290 if ( !KMessageBox::shouldBeShownContinue( QString::fromLatin1( "checkWhetherAllImagesIncludesSize" ) ) )
291 return;
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 );
323 return info;
326 QDomElement XMLDB::FileReader::readConfigFile( const QString& configFile )
328 QDomDocument doc;
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") );
344 else {
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 );
357 else {
358 if ( !file.open( QIODevice::ReadOnly ) ) {
359 KMessageBox::error( messageParent(), i18n("Unable to open '%1' for reading", configFile ), i18n("Error Running Demo") );
360 exit(-1);
363 QString errMsg;
364 int errLine;
365 int errCol;
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 ) );
369 exit(-1);
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 ) );
377 exit(-1);
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() ) );
383 exit(-1);
386 file.close();
387 return top;
390 QString XMLDB::FileReader::unescape( const QString& str )
392 QString tmp( str );
393 tmp.replace( QString::fromLatin1( "_" ), QString::fromLatin1( " " ) );
394 return tmp;
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();