For the past years I used the hamster timetracker in the organization I work and for some of my personal projects, but since the last year it was removed from the debian repositories and most of their builds were broken. So after some time thinking about it I set myself to build an alternative.
The focus on implementation was to:
- Implement the main features I use regularly.
- Reduce the number of dependencies and build steps so it is easier to make sure the application will still be able to be install in the future.
- Have a small resources usage so I can have it open all day without thinking about it.
- Be cross-platform so it works in more machines.
For the tools, I needed each one of them to be simple to compile, light on resources and cross-platform. The main points were dependencies would simplify my work were:
- The building of the graphical interface with menus, text, themes, etc.
- The storage, to persist the works and projects data.
- The support of dates in multiple formats.
- Parsing, exposing flags and options in command line.
Following my last experiments with graphical interfaces I found some cool alternatives. From those I selected the tekui toolkit, it is cross-platform, lightweight, has most of the UI elements I need and is simple to use with Lua.
Since this is a single user application and I wanted to be able to run it isolated in a folder without needing to install it to the system, I decided to use sqlite as database to have persistance of the tasks data. Sqlite is just a single C file, it's lightweight, easy to compile and the database for the application is just a regular file. Also to interact with it from lua the lsqlite binding can be used.
To have dates in a sqlite database one possible approach is to have the dates in the ISO8601 format. Also we need to check the duration of tasks and use dates for multiple other cases. With these needs in mind I selected the date library from Tieske. It is just one lua file with handling for multiple date formats, durations and support for ISO8601 in Lua.
For the creation and exposing of a command line interface the lua argparse library can be used. It is only a lua file, and allows a simple way to parse and register commands.
One of the initial concerns was to guarantee that the application always asks for the tasks data to the database. So when we have a GUI and a CLI operating on the data it is updated.
To have this possibility of having a GUI and a CLI also all the logic which implements each of the use cases should be isolated, be the only place modifying the database, and receive only the specific data needed for its actions.
With this in mind and having all the objectives set, the next step was starting the implementation. So I started by defining the main d-tracker features that I used:
- Stop task in progress
- Start task
- Edit the start and end time of an individual task
- Associate a task with a project
- Export filtered tasks to XML with the same format as the hamster timetracker
- Listing of tasks per range of days
- Project/Task Statistics by range of days
- Autocomplete in task creation
The implementation of the wanted features was done without much trouble. Some of the main points that gave me more work during the implementation were:
- Inputs with placeholders - Tekui had input fields but it missed the possibility to add a placeholder value. Here I changed the style of the text in the input field until the field is pressed and the user starts writing text.
- Inputs with autocomplete - The toolkit had popups for the select fields and for other things, but it lacked a popup associated with an input field. I implemented the management of the popup according with the input text and with the tasks and projects coming from the database.
- Exporting XML with escaped data - Since I just needed to export XML and not import it, the implementation was just creating a simple template that is valid XML and which conforms with the hamster format. But this would allow for injection in the XML, so I had to escape the chars ", ', <, > and &.
- Style the window - The graphical toolkit allows the styling of the application using a subset of CSS. The initial style was implemented to resemble the hamster one so the users can more easily switch from one to another.
- Validations of dates for tasks to not overlap - This was a situation I had to take special care to make sure that only one task may be running at any moment, multiple unit tests were made to validate that no tasks overlap and a warning popup window was created to report any possible conflicts. Hamster had multiple bugs with this, allowing for tasks to overlap the time of another and when the user changed the time it changed multiple other tasks without warning, creating new problems without the user noticing.
- Icon for the window in Linux - In linux the icon for the application is put in the folder /usr/share/pixmaps/ and then it is mapped in the /usr/share/applications/ folder. While that adds the application to the applications bar and puts icons there it still doesn't work for the window. So I had to create a script to transform the icon to the ARGB color format, pick the binary data and compile it statically in the window code of the tekui X11 backend.
Creating multiple themes was simple, the GUI toolkit allows for creating themes in CSS and already had multiple base themes. This way the base theme was made while trying to follow the hamster look and most of the other themes are the base tekui themes adapted to d-tracker elements.
The following themes were created:
Plugins were created to make a clear distinction that the core of the system doesn't depend on them and they can be optionally deactivated. The plugins can register hooks for events which will be triggered by the main application actions.
The current events which the plugins can listen to are:
- INIT - Configuration loaded, migrations setup, before UI starts
- UI_STARTED - Application UI is now up
- CLOSE - Application will close now
- TASK_CREATED - A new task was created
- TASK_STOP - The current task was stopped
- TASK_EDIT - Fields of a task changed
- TASK_DELETE - A task was deleted
- PROJECT_CREATED - A new project was created
- XML_EXPORT - Tasks were exported to a xml file
- PLUGIN_SELECT - The current plugin was selected in the plugins list in the top menubar
Right now the only plugin that was implemented was the Theme Switcher plugin. This plugin allows for switching the current theme directly in the UI without having to edit configuration files.
This plugin registered hooks for:
- INIT - To set the theme selected in the plugin when starting.
- UI_STARTED - To register the window of the plugin with the list of the existing themes and the option to change them.
- PLUGIN_SELECT - To display the window of the plugin.
To allow for users who like to do everything in the terminal to use d-tracker and to allow integration with other tools for automation, a CLI interface was implemented with the help of argparse.
This interface made available the main controllers already implemented to the GUI but now from the command line. The following commands were created:
list-today-tasks List today tasks list-tasks List tasks between a time range list-projects Lists the projects available in the database delete-task Delete a specific task add-task Add a new task edit-task-time Edit the time of a specific time edit-task-description Edit the description of a specific task edit-task-project Edit the project of a specific task export-today-xml Export today tasks to a xml file export-xml Export tasks in a time range to a xml file stop-in-progress Stop the current task in progress
Unit tests were implemented for all controllers, to validate that the database operations work well and for every cli command. New tests are also added case by case when new errors are found to avoid regressions.
To implement the unit tests busted was used. It's a really nice library that tells the developer to define the tests as a specification and when the code passes the test it means it passed the specification. You can check an example here.
This setup was really simple, just a YAML file in the path ".github/workflows/" with the instructions needed to install the dependencies in the last ubuntu, compile d-tracker and run the tests. You can check it here.
Tests were run locally while in development but I also took advantage of the github workflows which are free for open source projects. This way I have the project being built and the unit tests being run for each commit and pull request in the repository, including the linux and windows builds.
The result really pleased me, by implementing d-tracker now:
- Even the scripting language is compiled during the build so we almost only need the gcc and the x11 libs to compile the whole application. Of course the other dependencies are bundled with the application, but most are fairly small. This makes building the application in the future really simple.
- The application uses about 10MB of RAM when open, uses almost no CPU when idle and when in usage the CPU usage is also low.
- Some of the hamster problems were solved, like being able to have multiple tasks overlapping or having the application changing unwanted tasks dates without warning the user.
- My main workflows now needs less clicks so it's faster and easy to use. Also other actions I did less commonly are easier with the d-tracker CLI.
- We have multiple possible themes to choose from.