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 }