<> <> = Hacking on Ubuntu Accomplishments = ||<>|| This page provides some guidance for how you can contribute to the Ubuntu Accomplishments system, help fix bugs, and make it better. Thanks to every one of you who contributes to help make it better for our users. = Where We Live = You can see a general overview of the entire project at: * https://launchpad.net/ubuntu-accomplishments You can also see an overview of all bugs at https://bugs.launchpad.net/ubuntu-accomplishments == Core Ubuntu Accomplishments System == Here is where you can find the different resources as part of the project: * Ubuntu Accomplishments Daemon - this is the core daemon. * Code - `bzr branch lp:ubuntu-accomplishments-daemon` * Bugs - https://bugs.launchpad.net/ubuntu-accomplishments-daemon * Ubuntu Accomplishments Viewer - this is a GTK+ graphical interface that allows to easily browse trophies via GUI. * Code - `bzr branch lp:ubuntu-accomplishments-viewer` * Bugs - https://bugs.launchpad.net/ubuntu-accomplishments-viewer * Ubuntu Accomplishments System - a wrapper project for Daemon & Viewer, end users can file bugs here, then they are re-assigned to proper project, depending on whether they affect the daemon or the viewer. * Bugs - https://bugs.launchpad.net/ubuntu-accomplishments-system * Ubuntu Community Accomplishments - this is a set of accomplishments for the Ubuntu community. * Code - `bzr branch lp:ubuntu-community-accomplishments` * Bugs - https://bugs.launchpad.net/ubuntu-community-accomplishments * Ubuntu Accomplishments Extra Information - a collection of translated files used to gather extra info for scripts. * Code - `bzr branch lp:ubuntu-accomplishments-extra-information` * Bugs - https://bugs.launchpad.net/ubuntu-accomplishments-extra-information == Ubuntu Accomplishments Web Gallery == This is the Django-based web gallery for displaying Ubuntu Accomplishments online. * Ubuntu Accomplishments Web Editor: * Code - https://code.launchpad.net/ubuntu-accomplishments-web * Bugs - https://bugs.launchpad.net/ubuntu-accomplishments-web = How To Help = Firstly, thanks for taking the interesting in helping! We are really excited about welcoming you to the project! We recommend you get started by following these steps: == Step 1: Install the Development System == First, get the code installed and running on your system. You can read instructions for how to do this [[Accomplishments/GetInvolved/DevelopmentSetup|here]]. == Step 2: Read the Hacking Guide == Now take a look over the hacking guide below to get a general idea of the system and how it works. You can augment this documentation by taking a look through the code and the commends in the code as well as the API documentation see below). == Step 3: Pick a Bug == A great way to get started is to fix a few bugs. This helps get you familiarized with the code and get to know how things work. We have identified a set of "bitesize" bugs that help you get started on something that is simpler to get the hang of things. You can find the bitesize bugs at the following URLs: * Viewer - https://bugs.launchpad.net/ubuntu-accomplishments-viewer/+bugs?field.tag=bitesize * Daemon - https://bugs.launchpad.net/ubuntu-accomplishments-daemon/+bugs?field.tag=bitesize We recommend starting with viewer bugs first as they are easier to solve. == Step 4: Create and Submit Your Fix == Most developers tend to fix a bug a in it's own branch. As an example, if you want to fix a viewer bug, you can check the code out with: {{{ bzr branch lp:ubuntu-accomplishments-viewer mv ubuntu-accomplishments-viewer my-bug-fix }}} This now means you have the viewer code in a branch called `my-bug-fix`. Now hack the code to fix the bug and you can then submit this branch to the project as the bug fix. Be sure to reach out to help on our IRC channel (`#ubuntu-accomplishments` on freenode) as well as our mailing list at https://launchpad.net/~ubuntu-accomplishments-contributors To submit your fix, you first need to ''push'' it to Launchpad (this means putting it online). First commit your changes in your branch: {{{ bzr commit }}} Now push it to Launchpad: {{{ bzr push lp:~// }}} As an example, to push to my (jonobacon) account for a Viewer fix, I would run: {{{ bzr push lp:~jonobacon/ubuntu-accomplishments-viewer/fix-for-234232 }}} The number I include there is the bug number (you can call your branch whatever you like). When the fix is online, now go to the project that the fix was for (e.g. https://code.launchpad.net/ubuntu-accomplishments-viewer) and you should see your branch in the code view. Select your branch and click ''Propose for merging''. = API Documentation = You can read documentation for the daemon's API from the perspective of someone hacking on the daemon at http://213.138.100.229/ubuntu-accomplishments-daemon/docs/_build/html/daemonref.html You can read the API docs for client and accomplishment developers (the daemon's DBUS API) at http://213.138.100.229/ubuntu-accomplishments-daemon/docs/_build/html/clientref.html = Hacking Guide = The Ubuntu Accomplishments system is inspired by accomplishments systems used in other systems such as the Sony Playstation 3 trophies, StackExchange Badges, and FitBit Badges. The goal here is to create an accomplishments system that is extensible enough to be used by by local applications on a computer and contributions made to the Ubuntu community. In performing this work, these are some core goals: * '''Extensible''' - this system should be flexible enough that accomplishments at a desktop level (e.g. “Sent Email From Thunderbird” or “Sent First Tweet In Gwibber”) can be achieved, as well as project contributions (e.g. “Filed First Bug”). * '''Discoverable''' - the user should be able to see what trophies are available and find out how to achieve them. * '''Highly Distributed''' - the solution should be as de-centralized as possible so as to reduce the need for an extensive central service. * '''Integrated''' - the solution should be tightly integrated into Ubuntu itself and not just a series of disconnected web-pages. there are two core types of accomplishments: * '''Local Accomplishments''' - accomplishments that are local to your specific computer and do not require verification elsewhere (e.g. completing a level in a game). * '''Global Accomplishments''' - accomplishments that require verification from a third party (e.g. filing your first bug in Ubuntu). All accomplishments have the same kind of format and schema, but local and global accomplishments differ in how they generate trophies. For local accomplishments (e.g. completing a level on a game on your system), the following files are involved: * '''.accomplishment''' - this file outlines some cosmetic details about the accomplishment such as the name (e.g. ‘Level 2 Completed’), an icon (e.g. ‘level2.png’) and what other accomplishments should be completed before this one can be unlocked (e.g. ‘mygame/level1complete’). * '''.trophy''' - this file is the generated trophy that represents the completed accomplishment. libaccomplishments-daemon generates it. Here the .accomplishment file describes the accomplishment and when that accomplishment occurs (e.g. in a game you complete Level 2) the app would call the accomplishments system to process the .accomplishment file and generate the .trophy. This trophy would be added to the Ubuntu One Share for the local trophies. For ''Global Accomplishments'' we need to poll external services to check if an accomplishment has been achieved (e.g. Filing Your First Bug in Launchpad). To perform this polling we need the following: * '''.accomplishment''' - this file outlines some cosmetic details about the accomplishment such as the title (e.g. ‘First Bug Filed’), an icon (e.g. ‘default.png’) and what other accomplishments should be completed before this one can be unlocked (e.g. ‘ubuntu-community/registered-on-launchpad’). * '''A script''' (for ''global accomplishments'') - a file that contains the logic for determining if an accomplishment has been achieved (e.g. for a ‘First Bug Filed’ accomplishment this is a small Python script that connects to Launchpad to see if you have filed a bug yet). At regular intervals, the daemon looks to see, for each user on the system, which global accomplishments they have not yet accomplished. For each accomplishment, the system then runs the corresponding script. A script is expected to exit with a specific exit code: * 0 - for “the accomplishment is now accomplished” * 1 - for “the accomplishment is not yet accomplished” * 2 - for “there was an error”. If the logic script indicates that the accomplishment was successfully accomplished, the system creates the appropriate .trophy file. However, because this is a global accomplishment, it needs to be validated by a separate service to ensure the user isn’t faking it. As such, this separate hosted service has the same script and verifies it, and then signs it if successfully verified. == General Notes == Ubuntu Accomplishments uses a client/server approach. We provide a daemon which can have any number of clients connect to it. As an example, we have a GUI GTK client and an Ubuntu Unity Lens client, both of which work by asking the daemon for data and displaying it. The daemon does almost all of the work required for the clients to be useful (more on this later). The daemon also works asynchronously and we use Twisted so we can perform this async work and not block the clients. Many of our projects (daemon, viewer, battery) are Quickly projects. This means that you can use Quickly commands such as `quickly edit`, `quickly run`, `quickly design`, and `quickly package` when you work on them. The daemon does not support `quickly run` and you can instead start it from your trunk branch with: {{{ twistd -noy bin/accomplishments-daemon }}} For those of you unfamiliar with `twistd`; it is a tool that allows you to start a service like a daemon and provides suitable logging. === Standardizing on Accomplishments IDs === Throughout the code you will see references to `accom_id` and you will see that many methods (particularly in the daemon) require this `accom_id`. This is referring to an ''Accomplishment ID'' and this is the standard way that we use to identify a particular accomplishment in a collection. If you take a look at a collection there are three different types of structure on them: * '''Collection''' (e.g. `ubuntu-community`) - this is a collection of accomplishments that live together in the same group. While the package may be called one thing (e.g. `ubuntu-community-accomplishments`, there is always a set name of the collection inside the package (`ubuntu-community`). You can see these names inside `/usr/share/accomplishments/accomplishments`. * '''Set''' (e.g. `general`) - this is a sub-directory in a collection that is used to group together similar types of accomplishments. For example `/usr/share/accomplishments/accomplishments/ubuntu-community/en/` shows the different sets (e.g infrastructure, general, quality). * '''Accomplishment''' (e.g. `registered-on-launchpad`) - this is the specific accomplishment. An accomplishment actually has three parts (the `.accomplishment` file, the script, and the test). All of these have the same name though, just with different extensions and locatons. For example: * Accomplishment: `/usr/share/accomplishments/accomplishments/ubuntu-community/en/infrstructure/registered-on-launchpad.accomplishment`. * Script: `/usr/share/accomplishments/scripts/ubuntu-community/infrstructure/registered-on-launchpad.py`. * Script: `/usr/share/accomplishments/tests/ubuntu-community/infrstructure/registered-on-launchpad`. (note how the name in the above examples is always `registered-on-launchpad`). An ''Accomplishment ID'' is merely the name of the accomplishment collection identifier and the name of the accomplishment identifier seperated by a forward slash '/'. For example: `ubuntu-community/registered-on-launchpad`. === File Structures === Ubuntu Accomplishments has a variety of different files that it depends on (e.g. configuration files, accomplishments etc). This section explains the schema of these files and what they do. ==== Configuration Files ==== Configuration lives in `~/.config/accomplishments/.accomplishments` and the file supports a number of items: || '''Item''' || '''Example''' || '''Description''' || || `has_u1` || `has_u1 = 1` || (bool) Set to 1 if we have detected that the user has an active Ubuntu One account. Set to 0 if no account has been detected. This is typically used to detect whether a client should ask the user to configure their Ubuntu One account.|| || `has_verif` || `has_verif = 1` || (bool) Set to 1 if we have detected that the user has an active Ubuntu One share set up for the trophy directory (which by default is `~/.local/share/accomplishments/trophies`. Set to 0 if no share has been detected and set up. This is typically used to detect whether a client should ask the user to configure the trophy share. || || `accom_path` || `accompath = /home/jono/accomplishments:/usr/share/accomplishments` || (string) The location(s) where accomplishments collections can be found. Each location can be separatred with a colon ( `:` ) and they are prioritized from left to right. By default `~/accomplishments` and `/usr/share/accomplishments` are configured. || || `trophypath` || `trophypath = /home/jono/.local/share/accomplishments/trophies` || (string) The location of the trophy path where trophies are stored (this is also typically an Ubuntu One share. This defaults to `~/.local/share/trophies`. || || `daemon_sessionstart` || daemon_sessionstart = false || (bool) In the client preferences the user can set this if they want the daemon to start on login. This adds an `autostart` file to `~/.config/autostart`. || || `extrainfo_seen` || `extrainfo_seen = 1` || (bool) When this is set to 0, the user is notified that extra information is required - we set this to 1 so that the user is not nagged to add missing extra information if they don't have it (e.g. Ask Ubuntu credentials if they don't use Ask Ubuntu). || ==== Accomplishments Files ==== Accomplishment files (`.accomplishment` files) have a set of ConfigParser fields. You can see what these fields do in the Accomplishments manual at https://wiki.ubuntu.com/Accomplishments/Creating/Guide/AccomplishmentFile ==== Trophy Files ==== Trophies are generated when an accomplishment has been detected and successfully completed. these files are added to the path found in `trophypath` in the configuration file (this defaults to `~/.local/share/accomplishments/trophies`. Trophies are added inside sub-directories by collection (e.g. `~/.local/share/accomplishments/trophies/ubuntu-community`. A `.trophy` file has the following fields (all of which are required): || '''Item''' || '''Example''' || '''Description''' || || `version` || `version = 0.2` || Points to the version of the trophy schema. Please note: this version is not neccessarily the version of the daemon/viewer - the daemon may be at version 1.2 and still be using the 0.2 schema. || || `id` || `id = ubuntu-community/registered-on-launchpad` || The accomplishment ID of the trophy in question. This uses the standard accomplishment format of `collection/filename`. || || `date-accomplished` || `date-accomplished = 2012-06-12 22:12` || The date when the `.trophy` was generated in the format ''YYYY-MM-DD HH:MM:SS''. || || `needs-signing` || `needs-signing = true` || If this is a global accomplishment and therefore needs verifying, this should be set to `true`. If this is a local accomplishment on your computer, it does not need signing and should be set to `false`. || || `needs-information` || `needs-information = launchpad-email` || A list of the extra-information files types that this accomplishment needs. || || || `launchpad-email = jono@ubuntu.com` || For each of the types specified in the above field, this points to the identification data for that type (this data is gathered by the GUI viewer and stored in `~/.local/share/accomplishments/trophies/.extrainformation`. || == The Daemon == The Daemon is where most of the work happens in the system. The daemon provides an extensive API (in `ubuntu-accomplishments-daemon/accomplishments/daemon/api.py`) that performs a series of functions: * Reading in accomplishments collections. * Reading and writing the configuration files and logging. * Running accomplishments scripts. * Creating `.trophy` files. * Receiving verified trophies and ensuring that they are valid. * Providing an API for clients so they can display trophies and information in different ways. An important part of the daemon is that it presents an API to clients. the logic for this is in `api.py` and these methods are exposed via DBUS to clients (the DBUS interface is specified in `dbusapi.py`). Methods inside `api.py` that start with a `_` (e.g. `_this_method()`) are internal methods that are not exposed to clients. === Code Structure === The Daemon has two core classes: * `Accomplishments()` - this is where the majority of the API and functionality lives. * `AsyncAPI()` - this is where methods that need to talk Asynchronously via Twisted live. These methods typically use Deferreds, so we keep them together in this class. In the `Accomplishments()` class the daemon has a few different types of methods: || '''Method''' || '''Starts With''' || '''Example''' || '''Description''' || || Internal || `_` || `_create_all_trophy_icons() `|| These are internal methods that are not exposed to clients via the DBUS API. || || Accessors || `get_` || `get_acc_title()` || These are methods that return data to clients. We generally try to not return huge globs of data but instead specific data to a specific query. You can see further naming here such as `get_acc_` for accomplishment related data, and `get_collection` for collection related data. || || Lists || `list_` || `list_collections` || This returns a collection of data for a particular query (such as a list of all collections. || There are of course various other methods that do not fit into these categories. '''Please note: most of the above types of method standardize on Accomplishment IDs as the data you pass it. See the previous section for how accomplishment IDs work; they are pretty straight-forward to use'''. :-) === Running === You can run the daemon and see it's output with: {{{ twistd -noy bin/accomplishments-daemon }}} Be sure that before you run this that the daemon isn't already running. You can kill any instances of it with: {{{ killall -9 twistd }}} ==== Creating test config instances ==== By default all the configuration in the daemon is stored in standard XDG freedesktop paths (e.g. `~/.config`, `~/.cache`, and `~/.local/share`). You may want to store this config elsewhere (e.g. in `/tmp` for testing). You can override this by specifying the `ACCOMPLISHMENTS_ROOT_DIR` and the path. For example: {{{ export ACCOMPLISHMENTS_ROOT_DIR=/tmp }}} We always append `accomplishments` to this path, so the resulting path for the above setting will be `/tmp/accomplishments` and then the `.config`, `.cache`, and `.local/share` dirs will be created there. === Logging === Logging in the daemon writes to `~/.cache/accomplishments/logs`. == The Viewer == The viewer is a GTK application that is written using Python and GTK3 (which uses GObject Introspection). The vast majority of the functionality in terms of getting accomplishment data, processing accomplishments that are verified etc is performed by the daemon. The viewer primarily takes data from the daemon and displays it in different ways.