Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework ExtendedStorage API and service plugin API to remove any Notebook::Ptr #30

Closed
wants to merge 8 commits into from

Conversation

dcaliste
Copy link
Contributor

@pvuorela, this is a proposition to remove completely the Notebook::Ptr style, using either notebook ids or reference to notebooks.

This is done in steps:

  • commit a498d17, keep API compatibility and use internally pointers (out) or references (in) at SqliteFormat level. The ownsership is taken by QSharedPointer() in SqliteStorage.
  • commit 252bf4f, keep API compatibility and move notebook modifications (UID) out of ExtendedStorage so it can use const references on Notebooks later.
  • commit f675e67, update ExtendedStorage API not to use Notebook::Ptr anymore. Internal storage is done with Notebook objects and they are copied on retrieval. I've changed also the ::deleteNotebook() API to take id instead of object. The same for the ::defaultNotebook(), it's now dealing with an id.
  • commit 6d1128b, completely nuke Notebook::Ptr and Notebook::List definition from the API. It requires in addition to update all service plugins.

What do you think ? Applying these API changes will imply to update:

  • nemo-qml-plugin-calendar, but only in the worker thread code,
  • buteo-sync-plugin-caldav, nothing painfull,
  • all Exchange plugins, no idea how painful it can be.

The changes in ExtendedStorage API will correct some weirdness (in my opinion) like the fact that you need to use the exact Notebook::Ptr object when updating, while any change to the DB file is actually detaching your Ptr from the one that is stored, thus making any update call fails.

src/notebook.h Outdated
/**
Constructs a new Notebook object.
*/
explicit Notebook();

explicit Notebook(const QString &name, const QString &description);
explicit Notebook(const QString &name, const QString &description,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Old thing, but the explicit keyword doesn't look needed in any of these. Remove if you feel like it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I've removed them.

}

if (!nb || d->mNotebooks.contains(nb->uid())) {
if (d->mNotebooks.contains(nb.uid())) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we guard for notebook validity somewhere around here?

Copy link
Contributor Author

@dcaliste dcaliste Apr 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you mentioned later, isValidNotebook() is more about testing if notebook is writable. But that's a good remark anyway, and I've added a commit that guard against writing to the database runtime only notebooks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meant here more Notebook::isValid() type of validity check. Now it looks like this will proceed adding a notebook without a uid.

if (!nb.isNull()) {
if (nb->isRunTimeOnly() || nb->isReadOnly()) {
const Notebook &nb = notebook(notebookUid);
if (nb.isValid()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an interesting method. Could assume it's the same as notebook(uid).isValid() but it's not exactly. Three cases using it, pondering if it's needed or should the name be adjusted a little, e.g. notebookWritable() or something to that direction.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, I've renamed the method to isWritableNotebook(). I makes more sense, I agree.

qCDebug(lcMkcal) << "Storage is empty, initializing";
createDefaultNotebook();
if (!setDefaultNotebook(Notebook(QString::fromLatin1("Defaul"), QString()))) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Defaul"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@@ -1800,7 +1807,7 @@ bool SqliteStorage::loadNotebooks()
sqlite3_stmt *stmt = NULL;
bool isDefault;

Notebook::Ptr nb;
Notebook *nb;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor detail, but I could move to the while() declaration to keep the scope minimal.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you suggested, I've turn the SqliteFormat method into returning a Notebook and not a pointer to one. I've updated here accordingly.

@@ -1298,10 +1340,10 @@ bool SqliteFormat::Private::insertCalendarProperty(const QByteArray &id,
}
//@endcond

Notebook::Ptr SqliteFormat::selectCalendars(sqlite3_stmt *stmt, bool *isDefault)
Notebook* SqliteFormat::selectCalendars(sqlite3_stmt *stmt, bool *isDefault)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, does the pointer return value save much when the result gets deleted anyway? Perhaps could alternatively just return a Notebook instance which wouldn't need the deletion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, it's now returning a Notebook instance. instead of a pointer on a Notebook instance.

@dcaliste
Copy link
Contributor Author

I'm propagating changes in CalDAV plugin at the moment and this is raising a concern : there could be cases where the notebook is read-only, but we would like to get the upstream changes. The current pattern is to get the corresponding Notebook::Ptr and to make it read/write, do the calendar modifications and save them. This is working without touching the read-only flag status in the DB itself because we're dealing with a pointer to the internal Notebook in the ExtendedStorage object and calling the isValidNotebook() in sqlitestorage.cpp will return true, even if the DB flag means read-only. The same scheme is done in the birthday plugin in contactsd for instance.

Now changing for Notebook references make the whole pattern to fail. Except if we flip the flag in the DB itself with a call to ExtendedStorage::updateNotebook() and set it back after changes. This is very ugly though and creates possibilities of race conditions where the UI reports writable birthday calendar during the modifications for instance...

What do you think about removing the || isReadOnly() from isValidNotebook() ? The read-only flag would only be used as a hint for UI operations, but not enforced at the DB where we can still do all desired modifications when calling save() ? I may rename the isValidNotebook() to isStoredNotebook() or something to reflect this ?

@dcaliste
Copy link
Contributor Author

Maybe an additional question regarding API changes : since we're not dealing with pointers anymore, I've found the need for a ExtendedStorage::containsNotebook() method, to know if we should call addNotebook() or updateNotebook(). One can still use mStorage->notebook(mNotebook.uid()).isValid() but it is overly verbose in my opinion and it requires a useless copy. But thinking more about it, we may drop addNotebook() and updateNotebook() completely and create a new storeNotebook() which internally update or add the notebook, depending if it already exists or not. What do you think ?

Copy link
Contributor

@pvuorela pvuorela left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about removing the || isReadOnly() from isValidNotebook() ? The read-only flag would only be used as a hint for UI operations, but not enforced at the DB where we can still do all desired modifications when calling save() ? I may rename the isValidNotebook() to isStoredNotebook() or something to reflect this ?

Makes sense removing the isReadOnly() check. Better consider that more as a hint towards application code. It's strange usage now when readonly status gets changed without updateNotebook() and it's assumed effective.

Maybe an additional question regarding API changes : since we're not dealing with pointers anymore, I've found the need for a ExtendedStorage::containsNotebook() method, to know if we should call addNotebook() or updateNotebook().
[...] But thinking more about it, we may drop addNotebook() and updateNotebook() completely and create a new storeNotebook() which internally update or add the notebook, depending if it already exists or not. What do you think ?

The containsNotebook() could nicely fit the API, I'm ok with such. For latter I do sort of like if the additions and updates are separated for keeping it clearer which one is happening.

All in all this is looking mostly fine, but haven't tested much beyond compilation. Would need some api adaptation PR at least for nemo-calendar. Suppose that's coming up too?

}

if (!nb || d->mNotebooks.contains(nb->uid())) {
if (d->mNotebooks.contains(nb.uid())) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meant here more Notebook::isValid() type of validity check. Now it looks like this will proceed adding a notebook without a uid.

}
const Notebook &nb = d->mNotebooks.value(nbid);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: extra space after =

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected.

@dcaliste
Copy link
Contributor Author

dcaliste commented May 9, 2022

It's strange usage now when readonly status gets changed without updateNotebook() and it's assumed effective.

Exactly, I've removed it's usage. I took the opportunity to actually completely remove isWritableNotebook() from the API and implement its behaviour where it was used :

  • in sqlite storage, it's just checking runtime status and existing associated notebook (if validating),
  • in service handler, to keep backward compatibility, it's checking runtime status and read-only one.

Would need some api adaptation PR at least for nemo-calendar. Suppose that's coming up too?

I didn't try it yet in QML bindings, but you can see it in action for buteo plugin : see sailfishos/buteo-sync-plugin-caldav#11

@dcaliste
Copy link
Contributor Author

dcaliste commented May 10, 2022

Since we're already modifying the API in servicehandler.h, I've added a commit 9e34637 that is simplifying the [send/update]Invitation() functions. The ExtendedCalendar::Ptr, ExtendedStorage::Ptr and Notebook::Ptr arguments were just there to get the Notebook object, either directly if the invitation was not part of the calendar, or by a fetch in the storage if the invitation was in the calendar. This can be simplified by only passing the notebook id and the storage. I've updated the nemo-qml-plugin-calendar PR accordingly.

@pvuorela do you know if there are other places where this ServiceHandler::send/updateInvitation API is in use ?

(*savedIncidences) << *it;
if (dbop == DBInsert || dbop == DBUpdate) {
const Notebook &notebook = mStorage->notebook(notebookUid);
// Nota here : allow to save/delete incidences in a read-only
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: "nota" sound like a typo.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was a contraction of latine "nota bene" ;) Maybe a bit over contracted actually. I've updated for a more English "notice:" construction.

@@ -1169,6 +1171,8 @@ int SqliteStorage::Private::loadIncidences(sqlite3_stmt *stmt1,
// << incidence->dtStart() << endDateTime
// << "in calendar";
count += 1;
} else {
delete incidence;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the above break; case leaking memory now since selectComponents() return value is owned by the caller? Pondering should it still just return Incidence::Ptr since incidences are going to end up as such and there are these cases where it's ignored.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. I've reverted these changes at the moment. Ideally it's in preparation of SqliteStorage not assuming that loaded incidences will be used in the same thread. But these changes can be done at that moment.

@dcaliste
Copy link
Contributor Author

I've fixed the conflict and checked that it compiles and pass the tests.

@pvuorela
Copy link
Contributor

With the simpler notebook api change in, think we could close this now.

@pvuorela pvuorela closed this Jan 25, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants