diff --git a/buho.pro b/buho.pro index 3e0f640..afe153a 100644 --- a/buho.pro +++ b/buho.pro @@ -1,100 +1,104 @@ QT += qml QT += quick QT += sql QT += widgets QT += quickcontrols2 CONFIG += ordered CONFIG += c++17 QMAKE_LINK += -nostdlib++ TARGET = buho TEMPLATE = app DESTDIR = $$OUT_PWD/ linux:unix:!android { message(Building for Linux KDE) QT += webengine LIBS += -lMauiKit } else:android { message(Building helpers for Android) QT += androidextras webview # include($$PWD/3rdparty/openssl/openssl.pri) include($$PWD/3rdparty/kirigami/kirigami.pri) include($$PWD/3rdparty/mauikit/mauikit.pri) DEFINES += STATIC_KIRIGAMI } else { message("Unknown configuration") } include($$PWD/QGumboParser/QGumboParser.pri) DEFINES += QT_DEPRECATED_WARNINGS SOURCES += \ main.cpp \ src/db/db.cpp \ src/buho.cpp \ src/linker.cpp \ src/syncing/syncer.cpp \ src/utils/htmlparser.cpp \ src/models/notes/notes.cpp \ + src/models/books/books.cpp \ + src/models/books/booklet.cpp \ src/models/links/links.cpp \ src/providers/nextnote.cpp \ RESOURCES += \ qml.qrc \ assets/assets.qrc HEADERS += \ src/db/db.h \ src/buho.h \ src/syncing/syncer.h \ src/utils/owl.h \ src/linker.h \ src/utils/htmlparser.h \ src/models/notes/notes.h \ + src/models/books/books.h \ + src/models/books/booklet.h \ src/models/links/links.h \ src/providers/nextnote.h \ src/providers/abstractnotesprovider.h INCLUDEPATH += \ src/utils/ \ src/providers/ \ src/syncing/ \ src/ # Additional import path used to resolve QML modules in Qt Creator's code model QML_IMPORT_PATH = # Additional import path used to resolve QML modules just for Qt Quick Designer QML_DESIGNER_IMPORT_PATH = # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target DISTFILES += \ 3rdparty/mauikit/src/android/AndroidManifest.xml \ 3rdparty/mauikit/src/android/build.gradle \ 3rdparty/mauikit/src/android/gradle/wrapper/gradle-wrapper.jar \ 3rdparty/mauikit/src/android/gradle/wrapper/gradle-wrapper.properties \ 3rdparty/mauikit/src/android/gradlew \ 3rdparty/mauikit/src/android/gradlew.bat \ 3rdparty/mauikit/src/android/res/values/libs.xml \ src/db/script.sql \ include($$PWD/install.pri) contains(ANDROID_TARGET_ARCH,armeabi-v7a) { ANDROID_PACKAGE_SOURCE_DIR = \ $$PWD/3rdparty/mauikit/src/android } diff --git a/src/db/script.sql b/src/db/script.sql index e71686b..99b5e9b 100644 --- a/src/db/script.sql +++ b/src/db/script.sql @@ -1,58 +1,59 @@ CREATE TABLE IF NOT EXISTS NOTES ( id TEXT PRIMARY KEY, title TEXT, url TEXT, color TEXT, favorite INT, pin INT, adddate DATE, modified DATE ); CREATE TABLE IF NOT EXISTS NOTES_SYNC ( id TEXT, server TEXT, user TEXT, stamp TEXT, PRIMARY KEY(server, stamp), FOREIGN KEY(id) REFERENCES NOTES(id) ); CREATE TABLE IF NOT EXISTS BOOKS ( title TEXT PRIMARY KEY, url TEXT, favorite INT, adddate DATE, modified DATE ); CREATE TABLE IF NOT EXISTS BOOKLETS ( id TEXT, book TEXT, url TEXT, title TEXT NOT NULL, +favorite INT, adddate DATE, modified DATE, PRIMARY KEY(id, book), FOREIGN KEY(book) REFERENCES BOOKS(title) ); CREATE TABLE IF NOT EXISTS BOOKLETS_SYNC ( id TEXT, server TEXT, user TEXT, stamp TEXT, PRIMARY KEY(server, stamp), FOREIGN KEY(id) REFERENCES BOOKLETS(id) ); CREATE TABLE IF NOT EXISTS LINKS ( url TEXT PRIMARY KEY, title TEXT, preview TEXT, color TEXT, favorite INT, pin INT, adddate DATE, modified DATE ); diff --git a/src/syncing/syncer.cpp b/src/syncing/syncer.cpp index 431f6f8..ad00307 100644 --- a/src/syncing/syncer.cpp +++ b/src/syncing/syncer.cpp @@ -1,655 +1,675 @@ #include "syncer.h" #include "db/db.h" #include "abstractnotesprovider.h" #include #ifdef STATIC_MAUIKIT #include "tagging.h" #include "fm.h" #include "mauiaccounts.h" #include "mauiapp.h" #else #include #include #include #include #endif Syncer::Syncer(QObject *parent) : QObject(parent), tag(Tagging::getInstance()), db(DB::getInstance()), provider(nullptr) { const auto m_account = MauiApp::instance()->getAccounts(); connect(m_account, &MauiAccounts::currentAccountChanged, [&](QVariantMap currentAccount) { this->setAccount(FMH::toModel(currentAccount)); }); } void Syncer::setAccount(const FMH::MODEL &account) { if(this->provider) this->provider->setCredentials(account); } void Syncer::setProvider(AbstractNotesProvider *provider) { this->provider = std::move(provider); this->provider->setParent(this); this->provider->disconnect(); this->setConections(); } void Syncer::insertNote(FMH::MODEL ¬e) { if(!this->insertNoteLocal(note)) { qWarning()<< "The note could not be inserted locally, " "therefore it was not attempted to insert it to the remote provider server, " "even if it existed."; return; } this->insertNoteRemote(note); emit this->noteInserted(note, {STATE::TYPE::LOCAL, STATE::STATUS::OK, "Note inserted on the DB locally"}); } void Syncer::updateNote(const QString &id, const FMH::MODEL ¬e) { if(!this->updateNoteLocal(id, note)) { qWarning()<< "The note could not be updated locally, " "therefore it was not attempted to update it on the remote server provider, " "even if it existed."; return; } //to update remote note we need to pass the stamp as the id const auto stamp = Syncer::noteStampFromId(this->db, id); if(!stamp.isEmpty()) this->updateNoteRemote(stamp, note); emit this->noteUpdated(note, {STATE::TYPE::LOCAL, STATE::STATUS::OK, "Note updated on the DB locally"}); } void Syncer::removeNote(const QString &id) { //to remove the remote note we need to pass the stamp as the id, //and before removing the note locally we need to retireved first const auto stamp = Syncer::noteStampFromId(this->db, id); if(!this->removeNoteLocal(id)) { qWarning()<< "The note could not be inserted locally, " "therefore it was not attempted to insert it to the remote provider server, " "even if it existed."; return; } if(!stamp.isEmpty()) this->removeNoteRemote(stamp); emit this->noteRemoved(FMH::MODEL(), {STATE::TYPE::LOCAL, STATE::STATUS::OK, "The note has been removed from the local DB"}); } void Syncer::getNotes() { const auto notes = this->collectAllNotes(); if(this->provider && this->provider->isValid()) this->provider->getNotes(); else qWarning()<< "Credentials are missing to get notes or the provider has not been set"; emit this->notesReady(notes); } void Syncer::getBooks() { const auto books = this->collectAllBooks(); if(this->provider && this->provider->isValid()) this->provider->getBooklets(); else qWarning()<< "Credentials are missing to get notes or the provider has not been set"; emit this->booksReady(books); } void Syncer::insertBook(FMH::MODEL &book) { if(!this->insertBookLocal(book)) { qWarning()<< "Could not insert Book, Syncer::insertBook"; return; } emit this->bookInserted(book, {STATE::TYPE::LOCAL, STATE::STATUS::OK, "Book inserted locally sucessfully"}); } void Syncer::getBooklet(const QString &bookId) { const auto res = this->db->getDBData(QString("select * from booklets where book = '%1'").arg(bookId)); emit this->bookletReady(res); } void Syncer::updateBooklet(const QString &id, const QString &bookId, FMH::MODEL &booklet) { if(!this->updateBookletLocal(id, bookId, booklet)) { qWarning()<< "The booklet could not be updated locally, " "therefore it was not attempted to update it on the remote server provider, " "even if it existed."; return; } // to update remote booklet we need to pass the stamp as the id const auto stamp = Syncer::bookletStampFromId(this->db, id); qDebug()<< "booklet stamp from id" << stamp; if(!stamp.isEmpty()) this->updateBookletRemote(stamp, bookId, booklet); emit this->bookletUpdated(booklet, {STATE::TYPE::LOCAL, STATE::STATUS::OK, "Booklet updated locally on the DB"}); } void Syncer::insertBooklet(const QString &bookId, FMH::MODEL &booklet) { if(!this->insertBookletLocal(bookId, booklet)) { qWarning()<< "Could not insert Booklet, Syncer::insertBooklet"; return; } this->insertBookletRemote(bookId, booklet); emit this->bookletInserted(booklet, {STATE::TYPE::LOCAL, STATE::STATUS::OK, "Booklet inserted locally sucessfully"}); } void Syncer::addId(FMH::MODEL &model) { const auto id = QUuid::createUuid().toString(); model[FMH::MODEL_KEY::ID] = id; } const QString Syncer::noteIdFromStamp(DB *_db, const QString &provider, const QString &stamp) { return [&]() -> QString { const auto data = _db->getDBData(QString("select id from notes_sync where server = '%1' AND stamp = '%2'").arg(provider, stamp)); return data.isEmpty() ? QString() : data.first()[FMH::MODEL_KEY::ID]; }(); } const QString Syncer::noteStampFromId(DB *_db, const QString &id) { return [&]() -> QString { const auto data = _db->getDBData(QString("select stamp from notes_sync where id = '%1'").arg(id)); return data.isEmpty() ? QString() : data.first()[FMH::MODEL_KEY::STAMP]; }(); } const QString Syncer::bookletIdFromStamp(DB *_db, const QString &provider, const QString &stamp) { return [&]() -> QString { const auto data = _db->getDBData(QString("select id from booklets_sync where server = '%1' AND stamp = '%2'").arg(provider, stamp)); return data.isEmpty() ? QString() : data.first()[FMH::MODEL_KEY::ID]; }(); } const QString Syncer::bookletStampFromId(DB *_db, const QString &id) { return [&]() -> QString { const auto data = _db->getDBData(QString("select stamp from booklets_sync where id = '%1'").arg(id)); return data.isEmpty() ? QString() : data.first()[FMH::MODEL_KEY::STAMP]; }(); } void Syncer::setConections() { connect(this->provider, &AbstractNotesProvider::noteInserted, [&](FMH::MODEL note) { qDebug()<< "STAMP OF THE NEWLY INSERTED NOTE" << note[FMH::MODEL_KEY::ID] << note; this->db->insert(OWL::TABLEMAP[OWL::TABLE::NOTES_SYNC], FMH::toMap(FMH::filterModel(note, {FMH::MODEL_KEY::ID, FMH::MODEL_KEY::STAMP, FMH::MODEL_KEY::USER, FMH::MODEL_KEY::SERVER}))); emit this->noteInserted(note, {STATE::TYPE::REMOTE, STATE::STATUS::OK, "Note inserted on server provider"}); }); connect(this->provider, &AbstractNotesProvider::bookletInserted, [&](FMH::MODEL booklet) { qDebug()<< "STAMP OF THE NEWLY INSERTED BOOKLET" << booklet[FMH::MODEL_KEY::ID] << booklet; this->db->insert(OWL::TABLEMAP[OWL::TABLE::BOOKLETS_SYNC], FMH::toMap(FMH::filterModel(booklet, {FMH::MODEL_KEY::ID, FMH::MODEL_KEY::STAMP, FMH::MODEL_KEY::USER, FMH::MODEL_KEY::SERVER}))); emit this->bookletInserted(booklet, {STATE::TYPE::REMOTE, STATE::STATUS::OK, "Booklet inserted on server provider"}); }); connect(this->provider, &AbstractNotesProvider::notesReady, [&](FMH::MODEL_LIST notes) { // qDebug()<< "SERVER NOETS READY "<< notes; //if there are no notes in the provider server, then just return if(notes.isEmpty()) return; qDebug()<< "NOETS READY << " << notes; // there might be two case scenarios: // the note exists locally in the db, so it needs to be updated with the server version // the note does not exists locally, so it needs to be inserted into the db for(const auto ¬e : notes) { const auto id = Syncer::noteIdFromStamp(this->db, this->provider->provider(), note[FMH::MODEL_KEY::ID]); // if the id is empty then the note does nto exists, so ithe note is inserted into the local db if(id.isEmpty()) { //here insert the note into the db auto __note = FMH::filterModel(note, {FMH::MODEL_KEY::TITLE, FMH::MODEL_KEY::CONTENT, FMH::MODEL_KEY::FAVORITE, FMH::MODEL_KEY::MODIFIED, FMH::MODEL_KEY::ADDDATE}); __note[FMH::MODEL_KEY::MODIFIED] = QDateTime::fromSecsSinceEpoch(note[FMH::MODEL_KEY::MODIFIED].toInt()).toString(Qt::TextDate); __note[FMH::MODEL_KEY::ADDDATE] = __note[FMH::MODEL_KEY::MODIFIED]; if(!this->insertNoteLocal(__note)) { qWarning()<< "Remote note could not be inserted to the local storage"; continue; } __note[FMH::MODEL_KEY::STAMP] = note[FMH::MODEL_KEY::ID]; __note[FMH::MODEL_KEY::USER] = this->provider->user(); __note[FMH::MODEL_KEY::SERVER] = this->provider->provider(); this->db->insert(OWL::TABLEMAP[OWL::TABLE::NOTES_SYNC], FMH::toMap(FMH::filterModel(__note, {FMH::MODEL_KEY::ID, FMH::MODEL_KEY::STAMP, FMH::MODEL_KEY::USER, FMH::MODEL_KEY::SERVER}))); + emit this->noteInserted(__note, {STATE::TYPE::LOCAL, STATE::STATUS::OK, "Note inserted on local db, from the server provider"}); + }else { //the note exists in the db locally, so update it auto __note = FMH::filterModel(note, {FMH::MODEL_KEY::TITLE, FMH::MODEL_KEY::CONTENT, FMH::MODEL_KEY::MODIFIED, FMH::MODEL_KEY::FAVORITE}); __note[FMH::MODEL_KEY::MODIFIED] = QDateTime::fromSecsSinceEpoch(note[FMH::MODEL_KEY::MODIFIED].toInt()).toString(Qt::TextDate); this->updateNoteLocal(id, __note); - emit this->noteInserted(note, {STATE::TYPE::LOCAL, STATE::STATUS::OK, "Note inserted on local db, from the server provider"}); + emit this->noteUpdated(__note, {STATE::TYPE::LOCAL, STATE::STATUS::OK, "Note updated on local db, from the server provider"}); } } emit this->notesReady(this->collectAllNotes()); }); connect(this->provider, &AbstractNotesProvider::bookletsReady, [&](FMH::MODEL_LIST booklets) { // qDebug()<< "SERVER NOETS READY "<< notes; //if there are no notes in the provider server, then just return if(booklets.isEmpty()) return; qDebug()<< "Booklets READY << " << booklets; // there might be two case scenarios: // the booklet exists locally in the db, so it needs to be updated with the server version // the booklet does not exists locally, so it needs to be inserted into the db for(const auto &booklet : booklets) { const auto id = Syncer::bookletIdFromStamp(this->db, this->provider->provider(), booklet[FMH::MODEL_KEY::ID]); //the id is actually the stamp id // if the id is empty then the booklet does not exists, so insert the booklet into the local db if(id.isEmpty()) { //here insert the note into the db auto __booklet = FMH::filterModel(booklet, {FMH::MODEL_KEY::TITLE, FMH::MODEL_KEY::CONTENT, FMH::MODEL_KEY::MODIFIED, FMH::MODEL_KEY::ADDDATE}); __booklet[FMH::MODEL_KEY::MODIFIED] = QDateTime::fromSecsSinceEpoch(booklet[FMH::MODEL_KEY::MODIFIED].toInt()).toString(Qt::TextDate); __booklet[FMH::MODEL_KEY::ADDDATE] = __booklet[FMH::MODEL_KEY::MODIFIED]; if(!this->insertBookletLocal(booklet[FMH::MODEL_KEY::CATEGORY], __booklet)) { qWarning()<< "Remote booklet could not be inserted to the local storage"; continue; } __booklet[FMH::MODEL_KEY::STAMP] = booklet[FMH::MODEL_KEY::ID]; __booklet[FMH::MODEL_KEY::USER] = this->provider->user(); __booklet[FMH::MODEL_KEY::SERVER] = this->provider->provider(); this->db->insert(OWL::TABLEMAP[OWL::TABLE::BOOKLETS_SYNC], FMH::toMap(FMH::filterModel(__booklet, {FMH::MODEL_KEY::ID, FMH::MODEL_KEY::STAMP, FMH::MODEL_KEY::USER, FMH::MODEL_KEY::SERVER}))); + emit this->bookletInserted(__booklet, {STATE::TYPE::LOCAL, STATE::STATUS::OK, "Booklet inserted on local db, from the server provider"}); }else { //the booklet exists in the db locally, so update it auto __booklet = FMH::filterModel(booklet, {FMH::MODEL_KEY::TITLE, FMH::MODEL_KEY::CONTENT, FMH::MODEL_KEY::MODIFIED, FMH::MODEL_KEY::FAVORITE}); + + __booklet[FMH::MODEL_KEY::ID] = id; + __booklet[FMH::MODEL_KEY::BOOK] = booklet[FMH::MODEL_KEY::CATEGORY]; + __booklet[FMH::MODEL_KEY::URL] = [&]()-> QString { + const auto data = this->db->getDBData(QString("select url from booklets where id = '%1'").arg(id)); + return data.isEmpty() ? QString() : data.first()[FMH::MODEL_KEY::URL]; }(); + + qDebug()<< " trying to update local booklets with url" << __booklet[FMH::MODEL_KEY::URL] << __booklet[FMH::MODEL_KEY::BOOK] << __booklet[FMH::MODEL_KEY::CONTENT] ; __booklet[FMH::MODEL_KEY::MODIFIED] = QDateTime::fromSecsSinceEpoch(booklet[FMH::MODEL_KEY::MODIFIED].toInt()).toString(Qt::TextDate); - this->updateNoteLocal(id, __booklet); - emit this->noteInserted(booklet, {STATE::TYPE::LOCAL, STATE::STATUS::OK, "Note inserted on local db, from the server provider"}); + this->updateBookletLocal(id, __booklet[FMH::MODEL_KEY::BOOK], __booklet); + emit this->bookletUpdated(__booklet, {STATE::TYPE::LOCAL, STATE::STATUS::OK, "Booklet updated on local db, from the server provider"}); } } emit this->booksReady(this->collectAllBooks()); //??? }); connect(this->provider, &AbstractNotesProvider::noteUpdated, [&](FMH::MODEL note) { const auto id = Syncer::noteIdFromStamp(this->db, this->provider->provider(), note[FMH::MODEL_KEY::ID]); if(!note.isEmpty()) this->updateNoteLocal(id, FMH::filterModel(note, {FMH::MODEL_KEY::TITLE})); emit this->noteUpdated(note, {STATE::TYPE::REMOTE, STATE::STATUS::OK, "Note updated on server provider"}); }); connect(this->provider, &AbstractNotesProvider::bookletUpdated, [&](FMH::MODEL booklet) { const auto id = Syncer::bookletIdFromStamp(this->db, this->provider->provider(), booklet[FMH::MODEL_KEY::ID]); if(!booklet.isEmpty()) - this->updateBookletLocal(id, booklet[FMH::MODEL_KEY::CATEGORY], FMH::filterModel(booklet, {FMH::MODEL_KEY::TITLE})); + { + booklet[FMH::MODEL_KEY::ID] = id; + booklet[FMH::MODEL_KEY::BOOK] = booklet[FMH::MODEL_KEY::CATEGORY]; + booklet[FMH::MODEL_KEY::URL] = [&]()-> QString { + const auto data = this->db->getDBData(QString("select url from booklets where id = '%1'").arg(id)); + return data.isEmpty() ? QString() : data.first()[FMH::MODEL_KEY::URL]; }(); + this->updateBookletLocal(id, booklet[FMH::MODEL_KEY::BOOK], FMH::filterModel(booklet, {FMH::MODEL_KEY::TITLE})); + } + emit this->bookletUpdated(booklet, {STATE::TYPE::REMOTE, STATE::STATUS::OK, "Booklet updated on server provider"}); }); connect(this->provider, &AbstractNotesProvider::noteRemoved, [&]() { emit this->noteRemoved(FMH::MODEL(), {STATE::TYPE::REMOTE, STATE::STATUS::OK, "The note has been removed from the remove server provider"}); }); } bool Syncer::insertNoteLocal(FMH::MODEL ¬e) { if(note.isEmpty()) { qWarning()<< "Could not insert note locally. The note is empty"; return false; } Syncer::addId(note); //adds a local id to the note // create a file for the note const auto __notePath = Syncer::saveNoteFile(OWL::NotesPath, note); if(__notePath.isEmpty()) { qWarning()<< "File could not be saved. Syncer::insertNoteLocal."; return false; } note[FMH::MODEL_KEY::URL] = __notePath.toString(); qDebug()<< "note saved to <<" << __notePath; auto __noteMap = FMH::toMap(FMH::filterModel(note, {FMH::MODEL_KEY::ID, FMH::MODEL_KEY::TITLE, FMH::MODEL_KEY::COLOR, FMH::MODEL_KEY::URL, FMH::MODEL_KEY::PIN, FMH::MODEL_KEY::FAVORITE, FMH::MODEL_KEY::MODIFIED, FMH::MODEL_KEY::ADDDATE})); if(this->db->insert(OWL::TABLEMAP[OWL::TABLE::NOTES], __noteMap)) { for(const auto &tg : note[FMH::MODEL_KEY::TAG].split(",", QString::SplitBehavior::SkipEmptyParts)) this->tag->tagAbstract(tg, OWL::TABLEMAP[OWL::TABLE::NOTES], note[FMH::MODEL_KEY::ID], note[FMH::MODEL_KEY::COLOR]); return true; } return false; } void Syncer::insertNoteRemote(FMH::MODEL ¬e) { if(this->provider && this->provider->isValid()) this->provider->insertNote(note); } bool Syncer::updateNoteLocal(const QString &id, const FMH::MODEL ¬e) { for(const auto &tg : note[FMH::MODEL_KEY::TAG]) this->tag->tagAbstract(tg, OWL::TABLEMAP[OWL::TABLE::NOTES], id); this->saveNoteFile(OWL::NotesPath, note); return this->db->update(OWL::TABLEMAP[OWL::TABLE::NOTES], FMH::toMap(FMH::filterModel(note, {FMH::MODEL_KEY::TITLE, FMH::MODEL_KEY::COLOR, FMH::MODEL_KEY::PIN, FMH::MODEL_KEY::MODIFIED, FMH::MODEL_KEY::FAVORITE})), QVariantMap {{FMH::MODEL_NAME[FMH::MODEL_KEY::ID], id}}); } void Syncer::updateNoteRemote(const QString &id, const FMH::MODEL ¬e) { if(this->provider && this->provider->isValid()) this->provider->updateNote(id, note); } bool Syncer::removeNoteLocal(const QString &id) { this->db->remove(OWL::TABLEMAP[OWL::TABLE::NOTES_SYNC], {{FMH::MODEL_NAME[FMH::MODEL_KEY::ID], id}}); return this->db->remove(OWL::TABLEMAP[OWL::TABLE::NOTES], {{FMH::MODEL_NAME[FMH::MODEL_KEY::ID], id}}); } void Syncer::removeNoteRemote(const QString &id) { if(this->provider && this->provider->isValid()) this->provider->removeNote(id); } bool Syncer::insertBookLocal(FMH::MODEL &book) { const auto __path = QUrl::fromLocalFile(OWL::BooksPath+book[FMH::MODEL_KEY::TITLE]); if(FMH::fileExists(__path)) { qWarning()<< "The directory for the book already exists. Syncer::insertBookLocal" << book[FMH::MODEL_KEY::TITLE]; return false; } if(!FM::createDir(QUrl::fromLocalFile(OWL::BooksPath), book[FMH::MODEL_KEY::TITLE])) { qWarning() << "Could not create directory for the given book name. Syncer::insertBookLocal" << book[FMH::MODEL_KEY::TITLE]; return false; } book[FMH::MODEL_KEY::URL] = __path.toString(); return(this->db->insert(OWL::TABLEMAP[OWL::TABLE::BOOKS], FMH::toMap(FMH::filterModel(book,{FMH::MODEL_KEY::URL, FMH::MODEL_KEY::TITLE, FMH::MODEL_KEY::FAVORITE, FMH::MODEL_KEY::ADDDATE, FMH::MODEL_KEY::MODIFIED} )))); } void Syncer::insertBookRemote(FMH::MODEL &book) { } bool Syncer::updateBookLocal(const QString &id, const FMH::MODEL &book) { return false; } void Syncer::updateBookRemote(const QString &id, const FMH::MODEL &book) { } bool Syncer::removeBookLocal(const QString &id) { return false; } void Syncer::removeBookRemote(const QString &id) { } bool Syncer::insertBookletLocal(const QString &bookId, FMH::MODEL &booklet) { qDebug()<< "trying to insert booklet" << booklet; if(bookId.isEmpty() || booklet.isEmpty()) { qWarning()<< "Could not insert booklet. Reference to book id or booklet are empty"; return false; } Syncer::addId(booklet); //adds a local id to the booklet if(!FMH::fileExists(QUrl::fromLocalFile(OWL::BooksPath+bookId))) { qWarning()<< "The book does not exists in the db or the directory is missing. Syncer::insertBookletLocal. " "Creating a new book registry" << bookId; FMH::MODEL __book; __book[FMH::MODEL_KEY::TITLE] = bookId; this->insertBook(__book); } const auto __bookletPath = Syncer::saveNoteFile(OWL::BooksPath+bookId+"/", booklet); if(__bookletPath.isEmpty()) { qWarning()<< "File could not be saved. Syncer::insertBookletLocal"; return false; } booklet[FMH::MODEL_KEY::URL] = __bookletPath.toString(); booklet[FMH::MODEL_KEY::BOOK] = bookId; qDebug()<< "booklet saved to <<" << __bookletPath; auto __bookletMap = FMH::toMap(FMH::filterModel(booklet, {FMH::MODEL_KEY::ID, FMH::MODEL_KEY::BOOK, FMH::MODEL_KEY::TITLE, FMH::MODEL_KEY::URL, FMH::MODEL_KEY::MODIFIED, FMH::MODEL_KEY::ADDDATE})); if(this->db->insert(OWL::TABLEMAP[OWL::TABLE::BOOKLETS], __bookletMap)) { // for(const auto &tg : booklet[FMH::MODEL_KEY::TAG].split(",", QString::SplitBehavior::SkipEmptyParts)) // this->tag->tagAbstract(tg, OWL::TABLEMAP[OWL::TABLE::NOTES], booklet[FMH::MODEL_KEY::ID], booklet[FMH::MODEL_KEY::COLOR]); return true; } return false; } void Syncer::insertBookletRemote(const QString &bookId, FMH::MODEL &booklet) { qDebug()<< "trying to insert booklet remotely" << (this->provider ? "provider exists" : "failed provider") << this->provider->isValid(); booklet[FMH::MODEL_KEY::CATEGORY] = bookId; if(this->provider && this->provider->isValid()) this->provider->insertBooklet(booklet); } bool Syncer::updateBookletLocal(const QString &id, const QString &bookId, const FMH::MODEL &booklet) { // for(const auto &tg : booklet[FMH::MODEL_KEY::TAG]) // this->tag->tagAbstract(tg, OWL::TABLEMAP[OWL::TABLE::NOTES], id); const QUrl __path = QFileInfo(booklet[FMH::MODEL_KEY::URL]).dir().path(); - qDebug()<< "Updating local txt file as"<< __path.toLocalFile(); const auto __bookletPath = Syncer::saveNoteFile(__path.toLocalFile()+"/", booklet); + qDebug()<< "Updating local txt file as"<< __path.toLocalFile() << __bookletPath; if(__bookletPath.isEmpty()) { qWarning()<< "File could not be saved. Syncer::insertBookletLocal"; return false; } return this->db->update(OWL::TABLEMAP[OWL::TABLE::BOOKLETS], FMH::toMap(FMH::filterModel(booklet, {FMH::MODEL_KEY::TITLE, - FMH::MODEL_KEY::MODIFIED})), + FMH::MODEL_KEY::MODIFIED, + FMH::MODEL_KEY::FAVORITE})), QVariantMap {{FMH::MODEL_NAME[FMH::MODEL_KEY::ID], id}, {FMH::MODEL_NAME[FMH::MODEL_KEY::BOOK], bookId}}); } void Syncer::updateBookletRemote(const QString &id, const QString &bookId, FMH::MODEL &booklet) { booklet[FMH::MODEL_KEY::CATEGORY] = bookId; if(this->provider && this->provider->isValid()) this->provider->updateBooklet(id, booklet); } bool Syncer::removeBookletLocal(const QString &id) { return false; } void Syncer::removeBookletRemote(const QString &id) { } const FMH::MODEL_LIST Syncer::collectAllNotes() { FMH::MODEL_LIST __notes = this->db->getDBData("select * from notes"); for(auto ¬e : __notes) note[FMH::MODEL_KEY::CONTENT] = this->noteFileContent(note[FMH::MODEL_KEY::URL]); return __notes; } const FMH::MODEL_LIST Syncer::collectAllBooks() { // return this->db->getDBData("select b.*, count(distinct bl.id) as count from books b inner join booklets bl on bl.book = b.id"); return this->db->getDBData("select * from books"); } const QUrl Syncer::saveNoteFile(const QString &dir, const FMH::MODEL &data) { if(data.isEmpty() /*|| !data.contains(FMH::MODEL_KEY::CONTENT)*/) { qWarning() << "the note is empty, therefore it could not be saved into a file"; return QUrl(); } QFile file(dir+data[FMH::MODEL_KEY::ID]+QStringLiteral(".txt")); file.open(QFile::WriteOnly); file.write(data[FMH::MODEL_KEY::CONTENT].toUtf8()); file.close(); return QUrl::fromLocalFile(file.fileName()); } const QString Syncer::noteFileContent(const QUrl &path) { if(!path.isLocalFile()) { qWarning()<< "Can not open note file, the url is not a local path"; return QString(); } QFile file(path.toLocalFile()); file.open(QFile::ReadOnly); const auto content = file.readAll(); file.close(); return QString(content); } diff --git a/src/views/books/BookletPage.qml b/src/views/books/BookletPage.qml index 97425d8..7645ef7 100644 --- a/src/views/books/BookletPage.qml +++ b/src/views/books/BookletPage.qml @@ -1,201 +1,201 @@ import QtQuick 2.9 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 import org.kde.mauikit 1.0 as Maui import org.kde.kirigami 2.7 as Kirigami Item { id: control - property var currentBooklet : ({}) + property var currentBooklet : null signal exit() onCurrentBookletChanged: { editor.document.load(currentBooklet.url) _drawerPage.title = currentBook.title - - } + } Maui.BaseModel { id: _bookletModel list: _booksList.booklet } Maui.Page { id: _page anchors.fill: parent anchors.rightMargin: _drawer.modal === false ? _drawer.contentItem.width * _drawer.position : 0 headBar.leftContent: [ ToolButton { icon.name: "go-previous" onClicked: control.exit() }, ToolButton { icon.name: "document-save" onClicked: { currentBooklet.content = editor.text currentBooklet.title = title.text _booksList.booklet.update(currentBooklet, _listView.currentIndex) } }, TextField { id: title Layout.fillWidth: true Layout.fillHeight: true // Layout.margins: space.medium placeholderText: qsTr("New chapter...") font.weight: Font.Bold font.bold: true font.pointSize: fontSizes.large text: currentBooklet.title // Kirigami.Theme.backgroundColor: selectedColor // Kirigami.Theme.textColor: Qt.darker(selectedColor, 2.5) // color: fgColor background: Rectangle { color: "transparent" } } ] Maui.Holder { id: _holder - visible: !_listView.count + visible: !_listView.count || !currentBooklet emoji: "qrc:/Type.png" emojiSize: iconSizes.huge isMask: false title : "Nothing to edit!" body: "Select a chapter or create a new one" } Maui.Editor { id: editor anchors.fill: parent visible: !_holder.visible } Maui.Dialog { id: _newChapter title: qsTr("New Chapter") message: qsTr("Create a new chapter for your current book. Give it a title") entryField: true onAccepted: { _booksList.booklet.insert({title: textEntry.text}) } } Connections { target: root onCurrentViewChanged: _drawer.close() } Kirigami.OverlayDrawer { id: _drawer edge: Qt.RightEdge width: Kirigami.Units.gridUnit * 16 height: parent.height - _page.headBar.height y: _page.headBar.height modal: !isWide visible: _holder.visible contentItem: Maui.Page { id: _drawerPage anchors.fill: parent headBar.visible: true headBar.rightContent: ToolButton { icon.name: "view-sort" } background: Rectangle { color: "transparent" } Maui.Holder { anchors.margins: space.huge visible: !_listView.count emoji: "qrc:/E-reading.png" emojiSize: iconSizes.huge isMask: false title : "This book is empty!" body: "Start by creating a new chapter for your book by clicking the + icon" } ListView { id: _listView anchors.fill: parent model: _bookletModel clip: true + + onCountChanged: + { + _listView.currentIndex = count-1 + control.currentBooklet = _booksList.booklet.get(_listView.currentIndex) + } + delegate: Maui.LabelDelegate { id: _delegate label: index+1 + " - " + model.title Connections { target:_delegate onClicked: { _listView.currentIndex = index - console.log("Booklet cliked:", _booksList.booklet.get(index).url, _booksList.booklet.get(index).content ) currentBooklet = _booksList.booklet.get(index) } } } - - } Rectangle { z: 999 anchors.bottom: parent.bottom anchors.margins: space.huge anchors.horizontalCenter: parent.horizontalCenter height: toolBarHeight width: height color: Kirigami.Theme.positiveTextColor radius: radiusV ToolButton { anchors.centerIn: parent icon.name: "list-add" icon.color: "white" onClicked: _newChapter.open() } } } - - } } - } diff --git a/src/widgets/NewNoteDialog.qml b/src/widgets/NewNoteDialog.qml index cf5ecc1..f71cf6a 100644 --- a/src/widgets/NewNoteDialog.qml +++ b/src/widgets/NewNoteDialog.qml @@ -1,173 +1,173 @@ import QtQuick 2.9 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.0 import org.kde.mauikit 1.0 as Maui import org.kde.kirigami 2.7 as Kirigami Maui.Dialog { id: control parent: parent heightHint: 0.95 widthHint: 0.95 maxWidth: 700*unit maxHeight: maxWidth - property string selectedColor : "#ffffe6" + property string selectedColor : Kirigami.Theme.backgroundColor property string fgColor: Qt.darker(selectedColor, 3) property bool showEditActions : false rejectButton.visible: false signal noteSaved(var note) page.padding: 0 Kirigami.Theme.backgroundColor: selectedColor Kirigami.Theme.textColor: fgColor headBar.middleContent: TextField { id: title Layout.fillWidth: true Layout.margins: space.medium placeholderText: qsTr("Title") font.weight: Font.Bold font.bold: true font.pointSize: fontSizes.large // Kirigami.Theme.backgroundColor: selectedColor // Kirigami.Theme.textColor: Qt.darker(selectedColor, 2.5) // color: fgColor background: Rectangle { color: "transparent" } } footBar.leftContent: [ ToolButton { id: pinButton icon.name: "pin" checkable: true icon.color: checked ? Kirigami.Theme.highlightColor : Kirigami.Theme.textColor // onClicked: checked = !checked }, ToolButton { id: favButton icon.name: "love" checkable: true icon.color: checked ? "#ff007f" : Kirigami.Theme.textColor }, ToolButton { icon.name: "document-share" onClicked: isAndroid ? Maui.Android.shareText(editor.body.text) : shareDialog.show(editor.body.text) icon.color: Kirigami.Theme.textColor }, ToolButton { icon.name: "document-export" icon.color: Kirigami.Theme.textColor }, ToolButton { icon.name: "entry-delete" icon.color: Kirigami.Theme.textColor } ] acceptText: qsTr("Save") rejectText: qsTr("Discard") onAccepted: { if(editor.body.text.length > 0) packNote() clear() } onRejected: clear() ColumnLayout { anchors.fill: parent Maui.Editor { id: editor Layout.fillHeight: true Layout.fillWidth: true Kirigami.Theme.backgroundColor: selectedColor Kirigami.Theme.textColor: Qt.darker(selectedColor, 2.5) stickyHeadBar: true headBar.leftContent: ToolButton { icon.name: "image" icon.color: control.Kirigami.Theme.textColor } headBar.rightContent: ColorsBar { onColorPicked: selectedColor = color } } Maui.TagsBar { id: tagBar Layout.fillWidth: true allowEditMode: true list.abstract: true list.key: "notes" onTagsEdited: list.updateToAbstract(tags) onTagRemovedClicked: list.removeFromAbstract(index) Kirigami.Theme.backgroundColor: "transparent" Kirigami.Theme.textColor: Kirigami.Theme.textColor } } onOpened: if(isMobile) editor.body.forceActiveFocus() function clear() { title.clear() editor.body.clear() close() } function fill(note) { title.text = note.title editor.body.text = note.content - selectedColor = note.color ? note.color : Kirigami.Theme.backgroundColor + control.selectedColor = note.color ? note.color : Kirigami.Theme.backgroundColor pinButton.checked = note.pin == 1 favButton.checked = note.favorite == 1 tagBar.list.lot= note.id open() } function packNote() { noteSaved({ id: notesView.currentNote.id, title: title.text.trim(), content: editor.body.text, color: selectedColor, tag: tagBar.getTags(), pin: pinButton.checked ? 1 : 0, favorite: favButton.checked ? 1 : 0, modified: new Date() }) } }