2
* This file is part of buteo-syncfw package
4
* Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
6
* Contact: Sateesh Kavuri <sateesh.kavuri@nokia.com>
8
* This library is free software; you can redistribute it and/or
9
* modify it under the terms of the GNU Lesser General Public License
10
* version 2.1 as published by the Free Software Foundation.
12
* This library is distributed in the hope that it will be useful, but
13
* WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
* Lesser General Public License for more details.
17
* You should have received a copy of the GNU Lesser General Public
18
* License along with this library; if not, write to the Free Software
19
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
25
#include "ProfileManager.h"
29
#include <QTextStream>
30
#include <QDomDocument>
32
#include "ProfileFactory.h"
33
#include "ProfileEngineDefs.h"
35
#include "LogMacros.h"
40
static const QString FORMAT_EXT = ".xml";
41
static const QString BACKUP_EXT = ".bak";
42
static const QString LOG_EXT = ".log";
43
static const QString LOG_DIRECTORY = "logs";
44
static const QString BT_PROFILE_TEMPLATE("bt_template");
46
const QString ProfileManager::DEFAULT_PRIMARY_PROFILE_PATH =
47
QDir::homePath() + "/.sync/profiles";
48
const QString ProfileManager::DEFAULT_SECONDARY_PROFILE_PATH =
51
// Private implementation class for ProfileManager.
52
class ProfileManagerPrivate
55
ProfileManagerPrivate(const QString &aPrimaryPath,
56
const QString &aSecondaryPath);
58
/*! \brief Loads a profile from persistent storage.
60
* \param aName Name of the profile to load.
61
* \param aType Type of the profile to load.
62
* \return The loaded profile. 0 if the profile was not found.
64
Profile *load(const QString &aName, const QString &aType);
66
/*! \brief Loads the synchronization log associated with the given profile.
68
* \param aProfileName Name of the sync profile whose log shall be loaded.
69
* \return The loaded log. 0 if the log was not found.
71
SyncLog *loadLog(const QString &aProfileName);
73
bool parseFile(const QString &aPath, QDomDocument &aDoc);
75
void restoreBackupIfFound(const QString &aProfilePath,
76
const QString &aBackupPath);
78
QDomDocument constructProfileDocument(const Profile &aProfile);
80
bool writeProfileFile(const QString &aProfilePath, const QDomDocument &aDoc);
82
QString findProfileFile(const QString &aName, const QString &aType);
84
bool createBackup(const QString &aProfilePath, const QString &aBackupPath);
86
bool matchProfile(const Profile &aProfile,
87
const ProfileManager::SearchCriteria &aCriteria);
89
bool matchKey(const Profile &aProfile,
90
const ProfileManager::SearchCriteria &aCriteria);
92
// Primary path for profiles.
95
// Secondary path for profiles.
96
QString iSecondaryPath;
103
using namespace Buteo;
105
ProfileManagerPrivate::ProfileManagerPrivate(const QString &aPrimaryPath,
106
const QString &aSecondaryPath)
107
: iPrimaryPath(aPrimaryPath),
108
iSecondaryPath(aSecondaryPath)
110
if (iPrimaryPath.endsWith(QDir::separator()))
112
iPrimaryPath.chop(1);
114
if (iSecondaryPath.endsWith(QDir::separator()))
116
iSecondaryPath.chop(1);
119
LOG_DEBUG("Primary profile path set to" << iPrimaryPath);
120
LOG_DEBUG("Secondary profile path set to" << iSecondaryPath);
123
Profile *ProfileManagerPrivate::load(const QString &aName, const QString &aType)
125
QString profilePath = findProfileFile(aName, aType);
126
QString backupProfilePath = profilePath + BACKUP_EXT;
129
Profile* profile = 0;
131
restoreBackupIfFound(profilePath, backupProfilePath);
133
if (parseFile(profilePath, doc))
136
profile = pf.createProfile(doc.documentElement());
138
if (QFile::exists(backupProfilePath))
140
QFile::remove(backupProfilePath);
144
LOG_WARNING("Failed to load profile:" << aName);
150
SyncLog *ProfileManagerPrivate::loadLog(const QString &aProfileName)
152
QString fileName = iPrimaryPath + QDir::separator() + Profile::TYPE_SYNC + QDir::separator() +
153
LOG_DIRECTORY + QDir::separator() + aProfileName + LOG_EXT + FORMAT_EXT;
155
if (!QFile::exists(fileName))
157
LOG_DEBUG("No sync log found for profile:" << aProfileName);
161
QFile file(fileName);
162
if (!file.open(QIODevice::ReadOnly))
164
LOG_WARNING("Failed to open sync log file for reading:"
170
if (!doc.setContent(&file)) {
172
LOG_WARNING("Failed to parse XML from sync log file:"
178
return new SyncLog(doc.documentElement());
181
bool ProfileManagerPrivate::matchProfile(const Profile &aProfile,
182
const ProfileManager::SearchCriteria &aCriteria)
184
bool matched = false;
186
const Profile *testProfile = &aProfile;
187
if (!aCriteria.iSubProfileName.isEmpty())
189
// Sub-profile name was given, request a sub-profile with a
190
// matching name and type.
191
testProfile = aProfile.subProfile(aCriteria.iSubProfileName,
192
aCriteria.iSubProfileType);
194
if (testProfile != 0)
196
matched = matchKey(*testProfile, aCriteria);
200
if (aCriteria.iType == ProfileManager::SearchCriteria::NOT_EXISTS)
210
else if (!aCriteria.iSubProfileType.isEmpty())
212
// Sub-profile name was empty, but type was given. Get all
213
// sub-profiles with the matching type.
214
QStringList subProfileNames =
215
aProfile.subProfileNames(aCriteria.iSubProfileType);
216
if (!subProfileNames.isEmpty())
219
foreach (const QString &subProfileName, subProfileNames)
221
testProfile = aProfile.subProfile(subProfileName,
222
aCriteria.iSubProfileType);
223
if (testProfile != 0 && matchKey(*testProfile, aCriteria))
232
if (aCriteria.iType == ProfileManager::SearchCriteria::NOT_EXISTS)
244
matched = matchKey(aProfile, aCriteria);
250
bool ProfileManagerPrivate::matchKey(const Profile &aProfile,
251
const ProfileManager::SearchCriteria &aCriteria)
253
bool matched = false;
255
if (!aCriteria.iKey.isEmpty())
257
// Key name was given, get a key with matching name.
258
QString value = aProfile.key(aCriteria.iKey);
262
if (aCriteria.iType == ProfileManager::SearchCriteria::NOT_EXISTS ||
263
aCriteria.iType == ProfileManager::SearchCriteria::NOT_EQUAL)
274
switch (aCriteria.iType)
276
case ProfileManager::SearchCriteria::EXISTS:
280
case ProfileManager::SearchCriteria::NOT_EXISTS:
284
case ProfileManager::SearchCriteria::EQUAL:
285
matched = (value == aCriteria.iValue);
288
case ProfileManager::SearchCriteria::NOT_EQUAL:
289
matched = (value != aCriteria.iValue);
300
if (aCriteria.iType == ProfileManager::SearchCriteria::NOT_EXISTS)
313
ProfileManager::SearchCriteria::SearchCriteria()
314
: iType(ProfileManager::SearchCriteria::EQUAL)
318
ProfileManager::SearchCriteria::SearchCriteria(const SearchCriteria &aSource)
319
: iType(aSource.iType),
320
iSubProfileName(aSource.iSubProfileName),
321
iSubProfileType(aSource.iSubProfileType),
323
iValue(aSource.iValue)
327
ProfileManager::ProfileManager(const QString &aPrimaryPath,
328
const QString &aSecondaryPath)
329
: d_ptr(new ProfileManagerPrivate(aPrimaryPath, aSecondaryPath))
333
ProfileManager::~ProfileManager()
339
Profile *ProfileManager::profile(const QString &aName, const QString &aType)
341
return d_ptr->load(aName, aType);
344
SyncProfile *ProfileManager::syncProfile(const QString &aName)
346
Profile *p = profile(aName, Profile::TYPE_SYNC);
347
SyncProfile *syncProfile = 0;
348
if (p != 0 && p->type() == Profile::TYPE_SYNC)
350
// RTTI is not allowed, use static_cast. Should be safe, because
352
syncProfile = static_cast<SyncProfile*>(p);
354
// Load and merge all sub-profiles.
355
expand(*syncProfile);
357
// Load sync log. If not found, create an empty log.
358
if (syncProfile->log() == 0)
360
SyncLog *log = d_ptr->loadLog(aName);
363
log = new SyncLog(aName);
365
syncProfile->setLog(log);
372
QStringList ProfileManager::profileNames(const QString &aType)
374
// Search for all profile files from the primary directory
376
QString nameFilter = QString("*") + FORMAT_EXT;
378
QDir dir(d_ptr->iPrimaryPath + QDir::separator() + aType);
379
QFileInfoList fileInfoList = dir.entryInfoList(QStringList(nameFilter),
380
QDir::Files | QDir::NoSymLinks);
381
foreach (const QFileInfo &fileInfo, fileInfoList)
383
names.append(fileInfo.completeBaseName());
387
// Search for all profile files from the secondary directory
389
QDir dir(d_ptr->iSecondaryPath + QDir::separator() + aType);
390
QFileInfoList fileInfoList = dir.entryInfoList(QStringList(nameFilter),
391
QDir::Files | QDir::NoSymLinks);
392
foreach (const QFileInfo &fileInfo, fileInfoList)
394
// Add only if the list does not yet contain the name.
395
QString profileName = fileInfo.completeBaseName();
396
if (!names.contains(profileName))
398
names.append(profileName);
406
QList<SyncProfile*> ProfileManager::allSyncProfiles()
408
QList<SyncProfile*> profiles;
410
QStringList names = profileNames(Profile::TYPE_SYNC);
411
foreach (const QString &name, names)
413
SyncProfile *p = syncProfile(name);
423
QList<SyncProfile*> ProfileManager::allVisibleSyncProfiles()
425
QList<SyncProfile*> profiles = allSyncProfiles();
426
QList<SyncProfile*> visibleProfiles;
427
foreach (SyncProfile *p, profiles)
431
visibleProfiles.append(p);
439
return visibleProfiles;
442
QList<SyncProfile*> ProfileManager::getSyncProfilesByData(
443
const QString &aSubProfileName,
444
const QString &aSubProfileType,
445
const QString &aKey, const QString &aValue)
447
QList<SyncProfile*> allProfiles = allSyncProfiles();
448
QList<SyncProfile*> matchingProfiles;
450
foreach (SyncProfile *profile, allProfiles)
452
Profile *testProfile = profile;
453
if (!aSubProfileName.isEmpty())
455
// Sub-profile name was given, request a sub-profile with a
456
// matching name and type.
457
testProfile = profile->subProfile(aSubProfileName, aSubProfileType);
459
else if (!aSubProfileType.isEmpty())
461
// Sub-profile name was empty, but type was given. Get the first
462
// sub-profile with the matching type.
463
QStringList subProfileNames =
464
profile->subProfileNames(aSubProfileType);
465
if (!subProfileNames.isEmpty())
467
testProfile = profile->subProfile(subProfileNames.first(),
476
if (0 == testProfile) // Sub-profile was not found.
480
continue; // Not a match, continue with next profile.
485
// Key name was given, get a key with matching name.
486
QString value = testProfile->key(aKey);
487
if (value.isNull() || // Key was not found.
488
(!aValue.isEmpty() && (value != aValue))) // Value didn't match
492
continue; // Not a match, continue with next profile.
496
// Match, add profile to the list to be returned.
497
matchingProfiles.append(profile);
500
return matchingProfiles;
503
QList<SyncProfile*> ProfileManager::getSyncProfilesByData(
504
const QList<SearchCriteria> &aCriteria)
506
QList<SyncProfile*> allProfiles = allSyncProfiles();
507
QList<SyncProfile*> matchingProfiles;
509
foreach (SyncProfile *profile, allProfiles)
515
foreach (const SearchCriteria &criteria, aCriteria)
517
if (!d_ptr->matchProfile(*profile, criteria))
526
matchingProfiles.append(profile);
535
return matchingProfiles;
538
QList<SyncProfile*> ProfileManager::getSyncProfilesByStorage(
539
const QString &aStorageName, bool aStorageMustBeEnabled)
541
QList<SearchCriteria> criteriaList;
543
// Require that the profile is not disabled.
544
// Profile is enabled by default. Comparing with enabled = true would
545
// not work, because the key may not exist at all, even if the profile
547
SearchCriteria profileEnabled;
548
profileEnabled.iType = SearchCriteria::NOT_EQUAL;
549
profileEnabled.iKey = KEY_ENABLED;
550
profileEnabled.iValue = BOOLEAN_FALSE;
551
criteriaList.append(profileEnabled);
553
// Profile must not be hidden.
554
SearchCriteria profileVisible;
555
profileVisible.iType = SearchCriteria::NOT_EQUAL;
556
profileVisible.iKey = KEY_HIDDEN;
557
profileVisible.iValue = BOOLEAN_TRUE;
558
criteriaList.append(profileVisible);
561
SearchCriteria onlineService;
562
onlineService.iType = SearchCriteria::EQUAL;
563
onlineService.iSubProfileType = Profile::TYPE_SERVICE;
564
// Service profile name is left empty. Key value is matched with all
565
// found service sub-profiles, though there should be only one.
566
onlineService.iKey = KEY_DESTINATION_TYPE;
567
onlineService.iValue = VALUE_ONLINE;
568
criteriaList.append(onlineService);
570
// Storage must be supported.
571
SearchCriteria storageSupported;
572
storageSupported.iSubProfileName = aStorageName;
573
storageSupported.iSubProfileType = Profile::TYPE_STORAGE;
574
if (aStorageMustBeEnabled)
576
// Storage must be enabled also. Storages are disabled by default,
577
// so we can compare with enabled = true.
578
storageSupported.iType = SearchCriteria::EQUAL;
579
storageSupported.iKey = KEY_ENABLED;
580
storageSupported.iValue = BOOLEAN_TRUE;
584
// Existence of the storage sub-profile is sufficient.
585
storageSupported.iType = SearchCriteria::EXISTS;
587
criteriaList.append(storageSupported);
589
return getSyncProfilesByData(criteriaList);
593
bool ProfileManager::save(const Profile &aProfile)
595
QDomDocument doc = d_ptr->constructProfileDocument(aProfile);
598
LOG_WARNING("No profile data to write");
602
// Create path for the new profile file.
604
dir.mkpath(d_ptr->iPrimaryPath + QDir::separator() + aProfile.type());
605
QString profilePath(d_ptr->iPrimaryPath + QDir::separator() +
606
aProfile.type() + QDir::separator() + aProfile.name() + FORMAT_EXT);
608
// Create a backup of the existing profile file.
609
QString oldProfilePath = d_ptr->findProfileFile(aProfile.type(), aProfile.name());
610
QString backupPath = profilePath + BACKUP_EXT;
612
if (QFile::exists(oldProfilePath) &&
613
!d_ptr->createBackup(oldProfilePath, backupPath))
615
LOG_WARNING("Failed to create profile backup");
618
bool profileWritten = false;
619
if (d_ptr->writeProfileFile(profilePath, doc))
621
QFile::remove(backupPath);
622
profileWritten = true;
626
LOG_WARNING("Failed to save profile:" << aProfile.name());
627
profileWritten = false;
630
return profileWritten;
633
SyncProfile *ProfileManager::createTempSyncProfile (const QString &destAddress, bool &saveNewProfile)
635
saveNewProfile = true;
636
if (destAddress.contains("USB")) { //USB - PCSUite no requirement to save profile
637
LOG_INFO("USB connect - pc");
638
saveNewProfile = false;
639
return new SyncProfile(destAddress);
642
BtHelper btHelp(destAddress);
643
QMap <QString , QVariant> mapVal = btHelp.getDeviceProperties();
644
uint classType = mapVal.value("Class").toInt();
645
uint pcsuiteClass = 0x100; //Major Device Class - Computer!
647
if (classType & pcsuiteClass) {
648
LOG_INFO("Device major class is Computer"); // not required to save profile
649
saveNewProfile = false;
650
return new SyncProfile(destAddress);
653
QString profileName = mapVal.value("Name").toString();
654
if (profileName.isEmpty()) {
655
//Todo : What to show if name is empty !!
657
profileName = QString ("qtn_sync_dest_name_device_default");
659
while (profileNames(Profile::TYPE_SYNC).contains(profileName)){
664
LOG_INFO("Profile Name :" << profileName);
665
SyncProfile *tProfile = syncProfile(BT_PROFILE_TEMPLATE);
666
Profile *service = tProfile->serviceProfile();
668
tProfile->setName(profileName);
669
tProfile->setEnabled(true);
670
tProfile->setBoolKey("hidden", false);
671
service->setKey(KEY_BT_ADDRESS, destAddress);
672
service->setKey(KEY_BT_NAME, "deviceName");
674
LOG_WARNING("No service profile, unable to update properties");
681
void ProfileManager::enableStorages (Profile &aProfile,
682
QMap<QString , bool> &aStorageMap)
685
QMapIterator<QString, bool> i(aStorageMap);
686
LOG_INFO("ProfileManager::enableStorages");
687
while (i.hasNext()) {
689
Profile *profile = aProfile.subProfile(i.key(), Profile::TYPE_STORAGE);
691
profile->setEnabled(i.value());
693
LOG_WARNING("No storage profile by key :" << i.key());
698
bool ProfileManager::remove(const QString &aName, const QString &aType)
700
bool success = false;
701
QString filePath = d_ptr->iPrimaryPath + QDir::separator() + aType + QDir::separator() + aName + FORMAT_EXT;
703
// Try to load profile without expanding it. We need to check from the
704
// profile data if the profile is protected before removing it.
705
Profile *p = d_ptr->load(aName, aType);
708
if (!p->isProtected())
710
success = QFile::remove(filePath);
712
QString logFilePath = d_ptr->iPrimaryPath + QDir::separator() + aType + QDir::separator() +
713
LOG_DIRECTORY + QDir::separator() + aName + LOG_EXT + FORMAT_EXT;
714
success = QFile::remove(logFilePath);
719
LOG_DEBUG( "Cannot remove protected profile:" << aName );
726
LOG_DEBUG( "Profile not found from the primary path, cannot remove:" << aName );
732
void ProfileManager::expand(Profile &aProfile)
734
if (aProfile.isLoaded())
735
return; // Already expanded.
737
// Load and merge sub-profiles.
738
int prevSubCount = 0;
739
QList<Profile*> subProfiles = aProfile.allSubProfiles();
740
int subCount = subProfiles.size();
741
while (subCount > prevSubCount)
743
foreach (Profile *sub, subProfiles)
745
if (!sub->isLoaded())
747
Profile *loadedProfile = profile(sub->name(), sub->type());
748
if (loadedProfile != 0)
750
aProfile.merge(*loadedProfile);
751
delete loadedProfile;
756
// No separate profile file for the sub-profile.
757
LOG_DEBUG( "Referenced sub-profile not found:" <<
759
LOG_DEBUG( "Referenced from:" << aProfile.name() <<
762
sub->setLoaded(true);
766
// Load/merge may have created new sub-profile entries. Those need
767
// to be loaded also. Loop if sub-profile count has changed.
768
prevSubCount = subCount;
769
subProfiles = aProfile.allSubProfiles();
770
subCount = subProfiles.size();
773
aProfile.setLoaded(true);
776
bool ProfileManager::saveLog(const SyncLog &aLog)
779
QString fullPath = d_ptr->iPrimaryPath + QDir::separator() + Profile::TYPE_SYNC + QDir::separator() +
781
dir.mkpath(fullPath);
782
QFile file(fullPath + QDir::separator() + aLog.profileName() + LOG_EXT + FORMAT_EXT);
784
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate))
786
LOG_WARNING("Failed to open sync log file for writing:"
792
QDomProcessingInstruction xmlHeading =
793
doc.createProcessingInstruction("xml",
794
"version=\"1.0\" encoding=\"UTF-8\"");
795
doc.appendChild(xmlHeading);
797
QDomElement root = aLog.toXml(doc);
800
LOG_WARNING("Failed to convert sync log to XML");
804
doc.appendChild(root);
806
QTextStream outputStream(&file);
808
outputStream << doc.toString(PROFILE_INDENT);
815
void ProfileManager::saveRemoteTargetId(Profile &aProfile,const QString& aTargetId )
817
LOG_DEBUG("saveRemoteTargetId :" << aTargetId);
818
aProfile.setKey (KEY_REMOTE_ID, aTargetId);
823
bool ProfileManager::rename(const QString &aName, const QString &aNewName)
826
// Rename the sync profile
827
QString source = d_ptr->iPrimaryPath + QDir::separator() + Profile::TYPE_SYNC + QDir::separator() +
829
QString destination = d_ptr->iPrimaryPath + QDir::separator() + Profile::TYPE_SYNC + QDir::separator() +
830
aNewName + FORMAT_EXT;
831
ret = QFile::rename(source, destination);
834
// Rename the sync log
835
QString sourceLog = d_ptr->iPrimaryPath + QDir::separator() + Profile::TYPE_SYNC + QDir::separator() +
836
LOG_DIRECTORY + QDir::separator() + aName + LOG_EXT + FORMAT_EXT;
837
QString destinationLog = d_ptr->iPrimaryPath + QDir::separator() + Profile::TYPE_SYNC + QDir::separator() +
838
LOG_DIRECTORY + QDir::separator() + aNewName + LOG_EXT + FORMAT_EXT;
839
ret = QFile::rename(sourceLog, destinationLog);
842
// Roll back the earlier rename
843
QFile::rename(destination, source);
848
LOG_WARNING("Failed to rename profile" << aName);
853
bool ProfileManager::saveSyncResults(QString aProfileName,
854
const SyncResults &aResults)
857
bool success = false;
859
SyncLog *log = d_ptr->loadLog(aProfileName);
862
// No log yet, create new.
863
log = new SyncLog(aProfileName);
868
log->addResults(aResults);
869
success = saveLog(*log);
877
bool ProfileManagerPrivate::parseFile(const QString &aPath, QDomDocument &aDoc)
879
bool parsingOk = false;
881
if (QFile::exists(aPath))
885
if (file.open(QIODevice::ReadOnly))
887
parsingOk = aDoc.setContent(&file);
892
LOG_WARNING("Failed to parse profile XML: " << aPath);
896
LOG_WARNING("Failed to open profile file for reading:" << aPath);
901
LOG_WARNING("Profile file not found:" << aPath);
909
QDomDocument ProfileManagerPrivate::constructProfileDocument(const Profile &aProfile)
912
QDomElement root = aProfile.toXml(doc);
916
LOG_WARNING("Failed to convert profile to XML");
920
QDomProcessingInstruction xmlHeading =
921
doc.createProcessingInstruction("xml",
922
"version=\"1.0\" encoding=\"UTF-8\"");
924
doc.appendChild(xmlHeading);
925
doc.appendChild(root);
931
bool ProfileManagerPrivate::writeProfileFile(const QString &aProfilePath,
932
const QDomDocument &aDoc)
934
QFile file(aProfilePath);
935
bool profileWritten = false;
937
if (file.open(QIODevice::WriteOnly | QIODevice::Truncate))
939
QTextStream outputStream(&file);
940
outputStream << aDoc.toString(PROFILE_INDENT);
942
profileWritten = true;
946
LOG_WARNING("Failed to open profile file for writing:" << aProfilePath);
947
profileWritten = false;
950
return profileWritten;
953
void ProfileManagerPrivate::restoreBackupIfFound(const QString &aProfilePath,
954
const QString &aBackupPath)
956
if (QFile::exists(aBackupPath))
958
LOG_WARNING("Profile backup file found. The actual profile may be corrupted.");
961
if (parseFile(aBackupPath, doc))
963
LOG_DEBUG("Restoring profile from backup");
964
QFile::remove(aProfilePath);
965
QFile::copy(aBackupPath, aProfilePath);
969
LOG_WARNING("Failed to parse backup file");
970
LOG_DEBUG("Removing backup file");
971
QFile::remove(aBackupPath);
976
bool ProfileManagerPrivate::createBackup(const QString &aProfilePath,
977
const QString &aBackupPath)
979
return QFile::copy(aProfilePath, aBackupPath);
982
QString ProfileManagerPrivate::findProfileFile(const QString &aName, const QString &aType)
984
QString fileName = aType + QDir::separator() + aName + FORMAT_EXT;
985
QString primaryPath = iPrimaryPath + QDir::separator() + fileName;
986
QString secondaryPath = iSecondaryPath + QDir::separator() + fileName;
988
if (QFile::exists(primaryPath))
992
else if (!QFile::exists(secondaryPath))
998
return secondaryPath;