discourse/discourse — pull request #1320 by ZogStriP (2013-08-13 21:18:13+00:00)
Title: Local avatars

:sparkles: This PR adds support for custom avatars :sparkles:

### Presentation

The first thing you will notice is that there's a `change` button next to your avatar in the preference page. If you click on it, we'll open the gravatar website so that you can change your avatar there:

![gravatar cta](https://f.cloud.github.com/assets/362783/957577/5f6e9fd2-0454-11e3-89ab-26cec6930204.png)

If your beloved forum administrator enables the `allow_uploaded_avatars` setting:

![site setting](https://f.cloud.github.com/assets/362783/957575/5f60922a-0454-11e3-8694-c4bccd47f6dd.png)

That `change` button will redirect you to the `/users/:username/preferences/avatar` page instead:

![change avatar](https://f.cloud.github.com/assets/362783/957576/5f5f0d88-0454-11e3-936a-59b632f65f9c.png)

After uploading your avatar, you will see that it has **immediately** changed **everywhere**!

![wolverine](https://f.cloud.github.com/assets/362783/957574/5f608bae-0454-11e3-9390-0846b4b31186.png)

When you upload a custom avatar, we'll auto-select it for you. Of course, you can select which one you want to use between the gravatar one and the one you just uploaded. Or you can upload another one.

### Technical details

**So what has been done to support this?**

Before, we relied solely on Gravatar. Gravatar does this nice thing that you give it the sha of an email address and the size of the image you want and it gives you an image back. Computing the user's email sha is pretty fast so there was no need to store that information anywhere.

With the requirements of supporting both gravatar and custom avatars as well as making sure users don't freak out when their avatar doesn't change immediately, we had to do it more efficiently.

All starts with these three fields added to the `user` model

- `uploaded_avatar_id` is used to link to the uploaded image record
- `uploaded_avatar_template` is used to hold the user's custom avatar template (ie. an url with a `{size}`)
- `use_uploaded_avatar` is used to know whether the user wants to use the gravatar or the uploaded avatar

So, when a user uploads an image to be used as her avatar, we'll store the original at the same place we store uploads (either locally or on S3). That's what the `uploaded_avatar_id` is for. Then we'll launch the `GenerateAvatars` asynchronous job and we'll return the url of the original image so that we can immediately display it (even if resized in the browser).

In the background, the `GenerateAvatars` job will generate a bunch of thumbnails of the avatar and update the user's avatar template (`uploaded_avatar_template`) to point to *scalable* thumbnails.

Right now we're generating a centered square thumbnails for the following sizes: `120`, `45`, `32`, `25` and `20` (that you can double for supporting retina images). How do we decide whether to show the normal or retina image? We [look at the](https://github.com/ZogStriP/discourse/blob/71c33086b4534397c07b0add597f95c37c3dd343/app/assets/javascripts/discourse/components/utilities.js#L60-L63) `devicePixelRatio` and if it's `>= 1.5`, then we'll use retina images, otherwise, we'll use the normal ones.

The trickiest part was to get the avatar when we only have the username at our disposal (ie. when quoting or replying to a user). It turns out, we [have the information available](https://github.com/ZogStriP/discourse/blob/71c33086b4534397c07b0add597f95c37c3dd343/app/assets/javascripts/discourse/components/bbcode.js#L256-L263) depending on the context we're running the code. When we're on the client-side, we can extract the avatar template from the post stream (using `lookupAvatarByPostNumber `). When we're on the server-side (ie. generating an email or cooking the post), we can retrieve the avatar by looking up the user by her username (using `lookupAvatar`).