File indexing completed on 2024-05-12 04:57:57

0001 /* ============================================================
0002 * Falkon - Qt web browser
0003 * Copyright (C) 2010-2018 David Rosca <nowrep@gmail.com>
0004 *
0005 * This program is free software: you can redistribute it and/or modify
0006 * it under the terms of the GNU General Public License as published by
0007 * the Free Software Foundation, either version 3 of the License, or
0008 * (at your option) any later version.
0009 *
0010 * This program is distributed in the hope that it will be useful,
0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013 * GNU General Public License for more details.
0014 *
0015 * You should have received a copy of the GNU General Public License
0016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0017 * ============================================================ */
0018 #include "bookmarks.h"
0019 #include "bookmarkitem.h"
0020 #include "bookmarksmodel.h"
0021 #include "bookmarkstools.h"
0022 #include "autosaver.h"
0023 #include "datapaths.h"
0024 #include "settings.h"
0025 #include "qztools.h"
0026 
0027 #include <QSaveFile>
0028 #include <QJsonDocument>
0029 #include <QMetaType>
0030 
0031 static const int bookmarksVersion = 1;
0032 
0033 Bookmarks::Bookmarks(QObject* parent)
0034     : QObject(parent)
0035     , m_autoSaver(nullptr)
0036 {
0037     m_autoSaver = new AutoSaver(this);
0038     connect(m_autoSaver, &AutoSaver::save, this, &Bookmarks::saveSettings);
0039 
0040     init();
0041     loadSettings();
0042 }
0043 
0044 Bookmarks::~Bookmarks()
0045 {
0046     m_autoSaver->saveIfNecessary();
0047     delete m_root;
0048 }
0049 
0050 void Bookmarks::loadSettings()
0051 {
0052     Settings settings;
0053     settings.beginGroup(QSL("Bookmarks"));
0054     m_showOnlyIconsInToolbar = settings.value(QSL("showOnlyIconsInToolbar"), false).toBool();
0055     m_showOnlyTextInToolbar = settings.value(QSL("showOnlyTextInToolbar"), false).toBool();
0056     settings.endGroup();
0057 }
0058 
0059 bool Bookmarks::showOnlyIconsInToolbar() const
0060 {
0061     return m_showOnlyIconsInToolbar;
0062 }
0063 
0064 bool Bookmarks::showOnlyTextInToolbar() const
0065 {
0066     return m_showOnlyTextInToolbar;
0067 }
0068 
0069 BookmarkItem* Bookmarks::rootItem() const
0070 {
0071     return m_root;
0072 }
0073 
0074 BookmarkItem* Bookmarks::toolbarFolder() const
0075 {
0076     return m_folderToolbar;
0077 }
0078 
0079 BookmarkItem* Bookmarks::menuFolder() const
0080 {
0081     return m_folderMenu;
0082 }
0083 
0084 BookmarkItem* Bookmarks::unsortedFolder() const
0085 {
0086     return m_folderUnsorted;
0087 }
0088 
0089 BookmarkItem* Bookmarks::lastUsedFolder() const
0090 {
0091     return m_lastFolder;
0092 }
0093 
0094 BookmarksModel* Bookmarks::model() const
0095 {
0096     return m_model;
0097 }
0098 
0099 bool Bookmarks::isBookmarked(const QUrl &url)
0100 {
0101     return !searchBookmarks(url).isEmpty();
0102 }
0103 
0104 bool Bookmarks::canBeModified(BookmarkItem* item) const
0105 {
0106     Q_ASSERT(item);
0107 
0108     return item != m_root &&
0109            item != m_folderToolbar &&
0110            item != m_folderMenu &&
0111            item != m_folderUnsorted;
0112 }
0113 
0114 QList<BookmarkItem*> Bookmarks::searchBookmarks(const QUrl &url) const
0115 {
0116     QList<BookmarkItem*> items;
0117     search(&items, m_root, url);
0118     return items;
0119 }
0120 
0121 QList<BookmarkItem*> Bookmarks::searchBookmarks(const QString &string, int limit, Qt::CaseSensitivity sensitive) const
0122 {
0123     QList<BookmarkItem*> items;
0124     search(&items, m_root, string, limit, sensitive);
0125     return items;
0126 }
0127 
0128 QList<BookmarkItem*> Bookmarks::searchKeyword(const QString &keyword) const
0129 {
0130     QList<BookmarkItem*> items;
0131     searchKeyword(&items, m_root, keyword);
0132     return items;
0133 }
0134 
0135 void Bookmarks::addBookmark(BookmarkItem* parent, BookmarkItem* item)
0136 {
0137     Q_ASSERT(parent);
0138     Q_ASSERT(parent->isFolder());
0139     Q_ASSERT(item);
0140 
0141     insertBookmark(parent, parent->children().count(), item);
0142 }
0143 
0144 void Bookmarks::insertBookmark(BookmarkItem* parent, int row, BookmarkItem* item)
0145 {
0146     Q_ASSERT(parent);
0147     Q_ASSERT(parent->isFolder());
0148     Q_ASSERT(item);
0149 
0150     m_lastFolder = parent;
0151     m_model->addBookmark(parent, row, item);
0152     Q_EMIT bookmarkAdded(item);
0153 
0154     m_autoSaver->changeOccurred();
0155 }
0156 
0157 bool Bookmarks::removeBookmark(BookmarkItem* item)
0158 {
0159     if (!canBeModified(item)) {
0160         return false;
0161     }
0162 
0163     m_model->removeBookmark(item);
0164     Q_EMIT bookmarkRemoved(item);
0165 
0166     m_autoSaver->changeOccurred();
0167     return true;
0168 }
0169 
0170 void Bookmarks::changeBookmark(BookmarkItem* item)
0171 {
0172     Q_ASSERT(item);
0173     Q_EMIT bookmarkChanged(item);
0174 
0175     m_autoSaver->changeOccurred();
0176 }
0177 
0178 void Bookmarks::setShowOnlyIconsInToolbar(bool state)
0179 {
0180     m_showOnlyIconsInToolbar = state;
0181     Q_EMIT showOnlyIconsInToolbarChanged(state);
0182     m_autoSaver->changeOccurred();
0183 }
0184 
0185 void Bookmarks::setShowOnlyTextInToolbar(bool state)
0186 {
0187     m_showOnlyTextInToolbar = state;
0188     Q_EMIT showOnlyTextInToolbarChanged(state);
0189     m_autoSaver->changeOccurred();
0190 }
0191 
0192 void Bookmarks::saveSettings()
0193 {
0194     Settings settings;
0195     settings.beginGroup(QSL("Bookmarks"));
0196     settings.setValue(QSL("showOnlyIconsInToolbar"), m_showOnlyIconsInToolbar);
0197     settings.setValue(QSL("showOnlyTextInToolbar"), m_showOnlyTextInToolbar);
0198     settings.endGroup();
0199 
0200     saveBookmarks();
0201 }
0202 
0203 void Bookmarks::init()
0204 {
0205     m_root = new BookmarkItem(BookmarkItem::Root);
0206 
0207     m_folderToolbar = new BookmarkItem(BookmarkItem::Folder, m_root);
0208     m_folderToolbar->setTitle(tr("Bookmarks Toolbar"));
0209     m_folderToolbar->setDescription(tr("Bookmarks located in Bookmarks Toolbar"));
0210 
0211     m_folderMenu = new BookmarkItem(BookmarkItem::Folder, m_root);
0212     m_folderMenu->setTitle(tr("Bookmarks Menu"));
0213     m_folderMenu->setDescription(tr("Bookmarks located in Bookmarks Menu"));
0214 
0215     m_folderUnsorted = new BookmarkItem(BookmarkItem::Folder, m_root);
0216     m_folderUnsorted->setTitle(tr("Unsorted Bookmarks"));
0217     m_folderUnsorted->setDescription(tr("All other bookmarks"));
0218 
0219     if (BookmarksTools::migrateBookmarksIfNecessary(this)) {
0220         // Bookmarks migrated just now, let's save them ASAP
0221         saveBookmarks();
0222     }
0223     else {
0224         // Bookmarks don't need to be migrated, just load them as usual
0225         loadBookmarks();
0226     }
0227 
0228     m_lastFolder = m_folderUnsorted;
0229     m_model = new BookmarksModel(m_root, this, this);
0230 }
0231 
0232 void Bookmarks::loadBookmarks()
0233 {
0234     const QString bookmarksFile = DataPaths::currentProfilePath() + QLatin1String("/bookmarks.json");
0235     const QString backupFile = bookmarksFile + QLatin1String(".old");
0236 
0237     QJsonParseError err;
0238     QJsonDocument json = QJsonDocument::fromJson(QzTools::readAllFileByteContents(bookmarksFile), &err);
0239     const QVariant res = json.toVariant();
0240 
0241     if (err.error != QJsonParseError::NoError || res.typeId() != QMetaType::QVariantMap) {
0242         if (QFile(bookmarksFile).exists()) {
0243             qWarning() << "Bookmarks::init() Error parsing bookmarks! Using default bookmarks!";
0244             qWarning() << "Bookmarks::init() Your bookmarks have been backed up in" << backupFile;
0245 
0246             // Backup the user bookmarks
0247             QFile::remove(backupFile);
0248             QFile::copy(bookmarksFile, backupFile);
0249         }
0250 
0251         // Load default bookmarks
0252         json = QJsonDocument::fromJson(QzTools::readAllFileByteContents(QSL(":data/bookmarks.json")), &err);
0253         const QVariant data = json.toVariant();
0254 
0255         Q_ASSERT(err.error == QJsonParseError::NoError);
0256         Q_ASSERT(data.typeId() == QMetaType::QVariantMap);
0257 
0258         loadBookmarksFromMap(data.toMap().value(QSL("roots")).toMap());
0259 
0260         // Don't forget to save the bookmarks
0261         m_autoSaver->changeOccurred();
0262     }
0263     else {
0264         loadBookmarksFromMap(res.toMap().value(QSL("roots")).toMap());
0265     }
0266 }
0267 
0268 void Bookmarks::saveBookmarks()
0269 {
0270     QVariantMap bookmarksMap;
0271 
0272 #define WRITE_FOLDER(name, mapName, folder) \
0273     QVariantMap mapName; \
0274     mapName.insert(QSL("children"), writeBookmarks(folder)); \
0275     mapName.insert(QSL("expanded"), folder->isExpanded()); \
0276     mapName.insert(QSL("expanded_sidebar"), folder->isSidebarExpanded()); \
0277     mapName.insert(QSL("name"), folder->title()); \
0278     mapName.insert(QSL("description"), folder->description()); \
0279     mapName.insert(QSL("type"), QSL("folder")); \
0280     bookmarksMap.insert(name, mapName);
0281 
0282     WRITE_FOLDER(QSL("bookmark_bar"), toolbarMap, m_folderToolbar)
0283     WRITE_FOLDER(QSL("bookmark_menu"), menuMap, m_folderMenu)
0284     WRITE_FOLDER(QSL("other"), unsortedMap, m_folderUnsorted)
0285 #undef WRITE_FOLDER
0286 
0287     QVariantMap map;
0288     map.insert(QSL("version"), bookmarksVersion);
0289     map.insert(QSL("roots"), bookmarksMap);
0290 
0291     const QJsonDocument json = QJsonDocument::fromVariant(map);
0292     const QByteArray data = json.toJson();
0293 
0294     if (data.isEmpty()) {
0295         qWarning() << "Bookmarks::saveBookmarks() Error serializing bookmarks!";
0296         return;
0297     }
0298 
0299     QSaveFile file(DataPaths::currentProfilePath() + QLatin1String("/bookmarks.json"));
0300     if (!file.open(QFile::WriteOnly)) {
0301         qWarning() << "Bookmarks::saveBookmarks() Error opening bookmarks file for writing!";
0302         return;
0303     }
0304 
0305     file.write(data);
0306     file.commit();
0307 }
0308 
0309 void Bookmarks::loadBookmarksFromMap(const QVariantMap &map)
0310 {
0311 #define READ_FOLDER(name, folder) \
0312     readBookmarks(map.value(name).toMap().value(QSL("children")).toList(), folder); \
0313     folder->setExpanded(map.value(name).toMap().value(QSL("expanded")).toBool()); \
0314     folder->setSidebarExpanded(map.value(name).toMap().value(QSL("expanded_sidebar")).toBool());
0315 
0316     READ_FOLDER(QSL("bookmark_bar"), m_folderToolbar)
0317     READ_FOLDER(QSL("bookmark_menu"), m_folderMenu)
0318     READ_FOLDER(QSL("other"), m_folderUnsorted)
0319 #undef READ_FOLDER
0320 }
0321 
0322 void Bookmarks::readBookmarks(const QVariantList &list, BookmarkItem* parent)
0323 {
0324     Q_ASSERT(parent);
0325 
0326     for (const QVariant &entry : list) {
0327         const QVariantMap map = entry.toMap();
0328         BookmarkItem::Type type = BookmarkItem::typeFromString(map.value(QSL("type")).toString());
0329 
0330         if (type == BookmarkItem::Invalid) {
0331             continue;
0332         }
0333 
0334         auto* item = new BookmarkItem(type, parent);
0335 
0336         switch (type) {
0337         case BookmarkItem::Url:
0338             item->setUrl(QUrl::fromEncoded(map.value(QSL("url")).toByteArray()));
0339             item->setTitle(map.value(QSL("name")).toString());
0340             item->setDescription(map.value(QSL("description")).toString());
0341             item->setKeyword(map.value(QSL("keyword")).toString());
0342             item->setVisitCount(map.value(QSL("visit_count")).toInt());
0343             break;
0344 
0345         case BookmarkItem::Folder:
0346             item->setTitle(map.value(QSL("name")).toString());
0347             item->setDescription(map.value(QSL("description")).toString());
0348             item->setExpanded(map.value(QSL("expanded")).toBool());
0349             item->setSidebarExpanded(map.value(QSL("expanded_sidebar")).toBool());
0350             break;
0351 
0352         default:
0353             break;
0354         }
0355 
0356         if (map.contains(QSL("children"))) {
0357             readBookmarks(map.value(QSL("children")).toList(), item);
0358         }
0359     }
0360 }
0361 
0362 QVariantList Bookmarks::writeBookmarks(BookmarkItem* parent)
0363 {
0364     Q_ASSERT(parent);
0365 
0366     QVariantList list;
0367 
0368     const auto children = parent->children();
0369     for (BookmarkItem* child : children) {
0370         QVariantMap map;
0371         map.insert(QSL("type"), BookmarkItem::typeToString(child->type()));
0372 
0373         switch (child->type()) {
0374         case BookmarkItem::Url:
0375             map.insert(QSL("url"), child->urlString());
0376             map.insert(QSL("name"), child->title());
0377             map.insert(QSL("description"), child->description());
0378             map.insert(QSL("keyword"), child->keyword());
0379             map.insert(QSL("visit_count"), child->visitCount());
0380             break;
0381 
0382         case BookmarkItem::Folder:
0383             map.insert(QSL("name"), child->title());
0384             map.insert(QSL("description"), child->description());
0385             map.insert(QSL("expanded"), child->isExpanded());
0386             map.insert(QSL("expanded_sidebar"), child->isSidebarExpanded());
0387             break;
0388 
0389         default:
0390             break;
0391         }
0392 
0393         if (!child->children().isEmpty()) {
0394             map.insert(QSL("children"), writeBookmarks(child));
0395         }
0396 
0397         list.append(map);
0398     }
0399 
0400     return list;
0401 }
0402 
0403 void Bookmarks::search(QList<BookmarkItem*>* items, BookmarkItem* parent, const QUrl &url) const
0404 {
0405     Q_ASSERT(items);
0406     Q_ASSERT(parent);
0407 
0408     switch (parent->type()) {
0409     case BookmarkItem::Root:
0410     case BookmarkItem::Folder: {
0411         const auto children = parent->children();
0412         for (BookmarkItem* child : children) {
0413             search(items, child, url);
0414         }
0415         break;
0416     }
0417 
0418     case BookmarkItem::Url:
0419         if (parent->url() == url) {
0420             items->append(parent);
0421         }
0422         break;
0423 
0424     default:
0425         break;
0426     }
0427 }
0428 
0429 void Bookmarks::search(QList<BookmarkItem*>* items, BookmarkItem* parent, const QString &string, int limit, Qt::CaseSensitivity sensitive) const
0430 {
0431     Q_ASSERT(items);
0432     Q_ASSERT(parent);
0433 
0434     if (limit == items->count()) {
0435         return;
0436     }
0437 
0438     switch (parent->type()) {
0439     case BookmarkItem::Root:
0440     case BookmarkItem::Folder: {
0441         const auto children = parent->children();
0442         for (BookmarkItem* child : children) {
0443             search(items, child, string, limit, sensitive);
0444         }
0445         break;
0446     }
0447 
0448     case BookmarkItem::Url:
0449         if (parent->title().contains(string, sensitive) ||
0450             parent->urlString().contains(string, sensitive) ||
0451             parent->description().contains(string, sensitive) ||
0452             parent->keyword().compare(string, sensitive) == 0
0453            ) {
0454             items->append(parent);
0455         }
0456         break;
0457 
0458     default:
0459         break;
0460     }
0461 }
0462 
0463 void Bookmarks::searchKeyword(QList<BookmarkItem*>* items, BookmarkItem* parent, const QString &keyword) const
0464 {
0465     Q_ASSERT(items);
0466     Q_ASSERT(parent);
0467 
0468     switch (parent->type()) {
0469     case BookmarkItem::Root:
0470     case BookmarkItem::Folder: {
0471         const auto children = parent->children();
0472         for (BookmarkItem* child : children)
0473             searchKeyword(items, child, keyword);
0474         break;
0475     }
0476 
0477     case BookmarkItem::Url:
0478         if (parent->keyword() == keyword)
0479             items->append(parent);
0480         break;
0481 
0482     default:
0483         break;
0484     }
0485 }