Quentier internals overview
This article is primarily for developers who would like to contribute to Quentier but aren't quite sure where to start. Here the high-level overview of the app design is explained.
First, let's briefly enumerate the kinds of technology used in Quentier project:
- CSS (a bit)
If you are familiar with any of these technologies, you are most likely able to help developing Quentier! However, note that there are some specific ways in which these technologies are used in the project. For example, currently the list of supported Qt versions ranges from Qt 4.8.6 to the latest and greatest Qt 5.x. However, Qt 4.8.6 is only really supported on Linux while on Windows and on Mac it's much more reasonable to use newer Qt 5.x.
Another point is that even though C++ is known as a hard to grasp language for system programmers, in Quentier project only a relatively simple subset of it is used - the use of exceptions is very limited, most part of the code is written in C++98 style with only a handful of features from C++11 standard employed. That makes is possible to build Quentier using as old compilers as gcc-4.5 or Visual C++ 2010. In future the support for such old compilers is going to be dropped at some point though in order to employ more useful features from more recent C++ standards.
The core functionality of Quentier is encapsulated in libquentier library which is distributed under the terms of GNU LGPL v.3. It has no tight bindings to Quentier application (otherwise it won't make much sense to make it a separate library at all) so it can be used by other applications as well. Libquentier provides the following essential functionality:
- Local storage of notes, notebooks, tags, saved searches, resources. The data elements of each kind can be added, updated, removed, listed, looked up etc.
- Synchronization of data from local storage with Evernote servers. Both full and partial synchronization algorithms are implemented.
- Note editor UI component which enables one to view and edit notes.
- Conversion between ENML and HTML used for presenting notes within the note editor.
- A bunch of utility functions and classes.
Libquentier's functionality is in turn based on QEverCloud library which essentially is the Qt-friendly replacement of the official Evernote C++ SDK: QEverCloud handles the transmission of data between Evernote servers and the outside world and also offers nice Qt-friendly API. A large part of QEverCloud's code was generated automatically from Evernote thrift IDL files. IDL stands for "interface definition language". Thrift was originally invented within Facebook and later on was contributed to Apache Foundation. Thrift IDL is a thing like pseudo-code only it focuses on the definitions of structs instead of commands. Thrift IDL code itself is not compiled into executable code but it can be parsed and converted to code in some real programming language. Normally the generation of code in real programming language is performed by thrift compiler but it doesn't have a Qt-friendly C++ backend so QEverCloud's sources are generated using a custom tool - QEverCloudGenerator. The Qt-friendliness of the generated code means that all the most convenient Qt's data types are used instead of their plain C++ counterparts so no conversion between different data types needs to be done. Most notably
QString is used instead of
The local storage within libquentier uses SQLite as its backend i.e. all user's data is stored within a SQLite database. However, it is just an implementation detail, the fact of SQLite usage is completely hidden from libquentier's public API. So in theory is is possible to change the database backend in future to whatever one is considered better - even to storage of plain text files. Internally SQLite is used via Qt's
The synchronization is implemented according to Evernote's document. That document explains a lot of details but some non-obvious subtleties are not mentioned, unfortunately, so I plan to write more about them in further posts. The synchronization of user's own accounts, public notebooks and notebooks shared by other users is fully supported.
ENML converter serves the needs of the note editor: it performs the conversion between ENML which is Evernote's format for note content storage and HTML. Currently this converter has some hardcoded parts to best serve the needs of the note editor. It would be nice to add some configurability to it in future.
Quentier app's source code is mostly about handling various aspects of user interface and orchestration of work performed by the code within libquentier. Here are the primary parts of Quentier app's code:
AccountManager: this class controls the discovery of available and last used accounts, switching between accounts and other account management details.
MainWindow: this class is the central class within the entire app and it represents, as its name suggests, the main window of Quentier app. It also works as the mediator between several other classes.
NoteEditorTabsAndWindowsCoordinator: this class maintains the list of open note editor widgets, both tabbed ones and editors in separate windows. For performance and memory consumption concerns tabbed note editor widgets are automatically closed as new tabbed note editors are opened i.e. the LRU cache of tabbed note editors is maintained. The contents of automatically closed note editor tabs are automatically saved, of course, so no loss of data occurs.
SystemTrayIconManager: this class, as its name suggests, maintains the Quentier's system tray icon and handles various actions which might be requested from the tray icon's context menu.
EnexImporterare two helper classes which orchestrate the libqentier's code performing export and import of notes to and from data in ENEX format.
- Models: classes from
src/modelsfolder within Quentier's source tree. These are models in Qt's model-view framework sense: each is a subclass of QAbstractItemModel. There are several such classes:
FavoritesModel- contains data about favorited data items within the current account
NotebookModel- contains data about notebooks within the current account
TagModel- contains data about tags within the current account
SavedSearchModel- contains data about saved searches within the current account
NoteModel- contains data about notes within the current account
NoteFilterModel- subclass of QSortFilterProxyModel, implements filtering over the list of notes (sorting is implemented within the
LogViewerModel- subclass of QAbstractTableModel, contains data parsed from Quentier's log file; serves for convenient visual presentation of the log file's contents
- Views: classes from
src/viewsfolder within Quentier's source tree. These are views in Qt's model-view framework sense: each is a subclass of QTreeView or QListView. There are several such classes:
ItemView- intermediate helper class within the inheritance chain between
QTreeViewand particular Quentier's view classes
NotebookItemView- implements various actions available via context menu on notebooks from this view as well as special behaviour on notebook selection - the list of notes is filtered by the currently selected notebook unless it is a linked notebook (which is the current technical limitation due to the possibility of name clashes between notebooks from user's own account and linked notebooks or between different linked notebooks)
TagItemView- implements various actions available via context menu on tags from this view
SavedSearchItemView- implements various actions available via context menu on saved searches from this view
DeletedNoteItemView- implements various actions available via context menu on deleted notes from this view
NoteListView- implements various actions available via context menu on non-deleted notes from this view
- Delegates: classes from
src/delegatesfolder within Quentier's source tree. These are delegates in Qt's model-view framework sense: each is a subclass of QStyledItemDelegate and implements customized rendering and/or editing of items corresponding to a particular view. I won't list them here since basically each mentioned view has its own delegate + there are some other delegates which are best understood by looking at how and where they are used within Quentier's source code.
To put things into perspective, Quentier primarily consists of three large parts:
- Local storage
These three parts interact with each other, primarily asynchronously, via signals and slots. Local storage and synchronization operate within their own threads of execution, the UI is processed primarily in the main (GUI) thread so the operations inside it need to be fast.
This article is also available as a blog post.