File indexing completed on 2024-05-12 16:39:40
0001 /* This file is part of the KDE project 0002 Copyright (C) 2011-2014 Jarosław Staniek <staniek@kde.org> 0003 0004 This program is free software; you can redistribute it and/or 0005 modify it under the terms of the GNU Library General Public 0006 License as published by the Free Software Foundation; either 0007 version 2 of the License, or (at your option) any later version. 0008 0009 This program is distributed in the hope that it will be useful, 0010 but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0012 Library General Public License for more details. 0013 0014 You should have received a copy of the GNU Library General Public License 0015 along with this program; see the file COPYING. If not, write to 0016 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0017 * Boston, MA 02110-1301, USA. 0018 */ 0019 0020 #include "KexiRecentProjects.h" 0021 #include "kexidbshortcutfile.h" 0022 #include "kexidbconnectionset.h" 0023 #include <kexi.h> 0024 0025 #include <KDbDriverManager> 0026 #include <KDbDriverMetaData> 0027 #include <KDbConnection> 0028 #include <KDbMessageHandler> 0029 0030 #include <QDebug> 0031 #include <QDir> 0032 #include <QStandardPaths> 0033 0034 //#define KexiRecentProjects_DEBUG 0035 0036 //! @internal 0037 class Q_DECL_HIDDEN KexiRecentProjects::Private 0038 { 0039 public: 0040 explicit Private(KexiRecentProjects *qq) 0041 : handler(0), q(qq), loaded(false) 0042 { 0043 } 0044 ~Private() 0045 { 0046 qDeleteAll(toDelete); 0047 } 0048 void load(); 0049 bool add(KexiProjectData *data, const QString& existingShortcutPath, 0050 bool deleteDuplicate = false); 0051 0052 KDbMessageHandler* handler; 0053 QMap<KexiProjectData*, QString> shortcutPaths; 0054 private: 0055 KexiRecentProjects *q; 0056 bool loaded; 0057 QString path; 0058 QMap<QString, KexiProjectData*> projectsForKey; 0059 QSet<KexiProjectData*> toDelete; 0060 }; 0061 0062 void KexiRecentProjects::Private::load() 0063 { 0064 if (loaded) 0065 return; 0066 if (!Kexi::isKexiInstance()) { 0067 // Do not show the list of documents if this is not really Kexi but a test app based on Kexi 0068 return; 0069 } 0070 loaded = true; 0071 #ifdef KexiRecentProjects_DEBUG 0072 qDebug() << "wait.."; 0073 sleep(2); 0074 #endif 0075 path = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) 0076 + "/kexi/recent_projects/"; 0077 QDir dir(path); 0078 if (!dir.mkpath(path)) { 0079 q->m_result.setMessage(xi18n("Could not create folder <filename>%1</filename> for " 0080 "storing recent projects information.", path)); 0081 return; 0082 } 0083 if (!dir.exists() || !dir.isReadable()) { 0084 return; 0085 } 0086 const QStringList shortcuts 0087 = dir.entryList(QStringList() << QLatin1String("*.kexis"), 0088 QDir::Files | QDir::NoSymLinks | QDir::Readable | QDir::CaseSensitive 0089 | QDir::Hidden // Hidden too because there can be names starting with 0090 // dot or hidden for without a clear reason 0091 ); 0092 #ifdef KexiRecentProjects_DEBUG 0093 qDebug() << shortcuts; 0094 #endif 0095 foreach (const QString& shortcutPath, shortcuts) { 0096 #ifdef KexiRecentProjects_DEBUG 0097 qDebug() << shortcutPath; 0098 #endif 0099 KexiProjectData *data = new KexiProjectData; 0100 bool ok = data->load(path + shortcutPath); 0101 #ifdef KexiRecentProjects_DEBUG 0102 qDebug() << "result:" << ok; 0103 #endif 0104 if (ok) { 0105 add(data, path + shortcutPath, true /*deleteDuplicate*/); 0106 } 0107 else { 0108 q->m_result = data->result(); 0109 delete data; 0110 } 0111 } 0112 } 0113 0114 static QString key(const KexiProjectData& data) 0115 { 0116 return KexiDBConnectionSet::key(*data.connectionData()) 0117 + ',' + data.databaseName(); 0118 } 0119 0120 bool KexiRecentProjects::Private::add(KexiProjectData *newData, 0121 const QString& existingShortcutPath, 0122 bool deleteDuplicate) 0123 { 0124 //qDebug() << *newData; 0125 KexiProjectData::List list(q->list()); // also loads it 0126 if (list.contains(newData)) 0127 return true; 0128 0129 // find similar project data 0130 QString newDataKey = key(*newData); 0131 #ifdef KexiRecentProjects_DEBUG 0132 qDebug() << "path:" << path << "newDataKey:" << newDataKey; 0133 qDebug() << "projectsForKey.keys():" << projectsForKey.keys(); 0134 qDebug() << "shortcutPaths.values():" << shortcutPaths.values(); 0135 #endif 0136 KexiProjectData* existingData = projectsForKey.value(newDataKey); 0137 QString shortcutPath = existingShortcutPath; 0138 if (existingData && existingData->lastOpened() < newData->lastOpened()) { 0139 if (q->takeProjectDataInternal(existingData)) { 0140 // this data will be replaced by similar 0141 #ifdef KexiRecentProjects_DEBUG 0142 qDebug() << "Existing data replaced by new similar:" 0143 << "\nexisting:" << *existingData 0144 << "\nnew:" << *newData; 0145 #endif 0146 if (deleteDuplicate) { 0147 QString fileToRemove(shortcutPaths.value(existingData)); 0148 0149 delete existingData; 0150 #ifdef KexiRecentProjects_DEBUG 0151 qDebug() << "Removing unnecessary file shortcut:" << fileToRemove; 0152 #endif 0153 if (!QFile::remove(fileToRemove)) { 0154 qWarning() << "Failed to remove unnecessary recent file shortuct:" 0155 << fileToRemove; 0156 } 0157 } 0158 else { // cannot be deleted now, remember 0159 toDelete.insert(existingData); 0160 } 0161 } 0162 if (shortcutPath.isEmpty()) { 0163 shortcutPath = shortcutPaths.value(existingData); // reuse this fileName 0164 } 0165 projectsForKey.remove(newDataKey); 0166 shortcutPaths.remove(existingData); 0167 } 0168 else { // no existing data or existing is newer 0169 if (existingData && existingData->lastOpened() >= newData->lastOpened()) { 0170 // the new data is older than existing 0171 // this data is replaced by similar 0172 #ifdef KexiRecentProjects_DEBUG 0173 qDebug() << "New data is older than existing - removing new:" 0174 << "\nexisting:" << *existingData 0175 << "\nnew:" << *newData; 0176 #endif 0177 if (deleteDuplicate) { 0178 delete newData; 0179 #ifdef KexiRecentProjects_DEBUG 0180 qDebug() << "Removing unnecessary file shortcut:" << existingShortcutPath; 0181 #endif 0182 if (!QFile::remove(existingShortcutPath)) { 0183 qWarning() << "Failed to remove unnecessary recent file shortuct:" 0184 << existingShortcutPath; 0185 } 0186 } 0187 else { // cannot be deleted now, remember 0188 toDelete.insert(newData); 0189 } 0190 return true; 0191 } 0192 else { 0193 #ifdef KexiRecentProjects_DEBUG 0194 qDebug() << "New data:" << *newData; 0195 #endif 0196 } 0197 if (shortcutPath.isEmpty()) { 0198 KDbConnectionData conn = *newData->connectionData(); 0199 KDbDriverManager manager; 0200 const KDbDriverMetaData *metaData = manager.driverMetaData(conn.driverId()); 0201 if (!metaData) { 0202 q->m_result = manager.result(); 0203 return false; 0204 } 0205 if (metaData->isFileBased()) { 0206 shortcutPath = QFileInfo(newData->databaseName()).fileName(); 0207 QFileInfo fi(shortcutPath); 0208 if (!fi.suffix().isEmpty()) { 0209 shortcutPath.chop(fi.suffix().length() + 1); 0210 } 0211 } else { 0212 shortcutPath = newData->databaseName(); 0213 if (!conn.hostName().isEmpty()) { 0214 shortcutPath += '_' + conn.hostName(); 0215 } 0216 } 0217 if (shortcutPath.startsWith('.')) { 0218 shortcutPath.prepend('_'); 0219 } 0220 shortcutPath = path + shortcutPath; 0221 int suffixNumber = 0; 0222 QString suffixNumberString; 0223 forever { // add "_{number}" to ensure uniqueness 0224 if (!QFile::exists(shortcutPath + suffixNumberString + QLatin1String(".kexis"))) 0225 break; 0226 suffixNumber++; 0227 suffixNumberString = QString("_%1").arg(suffixNumber); 0228 } 0229 shortcutPath += (suffixNumberString + QLatin1String(".kexis")); 0230 } 0231 } 0232 projectsForKey.insert(newDataKey, newData); 0233 shortcutPaths.insert(newData, shortcutPath); 0234 q->addProjectDataInternal(newData); 0235 0236 #ifdef KexiRecentProjects_DEBUG 0237 qDebug() << "existingShortcutPath:" << existingShortcutPath; 0238 qDebug() << "shortcutPath:" << shortcutPath; 0239 #endif 0240 bool result = true; 0241 if (existingShortcutPath.isEmpty()) { 0242 result = newData->save(shortcutPath, false /* !savePassword */); 0243 } 0244 #ifdef KexiRecentProjects_DEBUG 0245 qDebug() << "result:" << result; 0246 #endif 0247 return result; 0248 } 0249 0250 KexiRecentProjects::KexiRecentProjects(KDbMessageHandler* handler) 0251 : KexiProjectSet() 0252 , d(new Private(this)) 0253 { 0254 d->handler = handler; 0255 } 0256 0257 KexiRecentProjects::~KexiRecentProjects() 0258 { 0259 delete d; 0260 } 0261 0262 void KexiRecentProjects::addProjectData(const KexiProjectData &data) 0263 { 0264 if (!Kexi::isKexiInstance()) { 0265 // Do not update the list of documents if this is not really Kexi but a test app based on Kexi 0266 return; 0267 } 0268 d->add(new KexiProjectData(data), QString() /*save new shortcut*/); 0269 } 0270 0271 void KexiRecentProjects::addProjectDataInternal(KexiProjectData *data) 0272 { 0273 KexiProjectSet::addProjectData(data); 0274 } 0275 0276 KexiProjectData* KexiRecentProjects::takeProjectDataInternal(KexiProjectData *data) 0277 { 0278 return KexiProjectSet::takeProjectData(data); 0279 } 0280 0281 KexiProjectData::List KexiRecentProjects::list() const 0282 { 0283 d->load(); 0284 return KexiProjectSet::list(); 0285 } 0286 0287 QString KexiRecentProjects::shortcutPath(const KexiProjectData& data) const 0288 { 0289 return d->shortcutPaths.value(const_cast<KexiProjectData*>(&data)); 0290 }