Skip to content

Commit 33c8d6e

Browse files
authored
Add universal API cache and implement proper fallbacks (ev3dev#178)
* Add universal API cache and harden API logic * Be more friendly to people with JS disabled * Make user cards functional when JS is disabled
1 parent 1c15ce5 commit 33c8d6e

File tree

7 files changed

+131
-74
lines changed

7 files changed

+131
-74
lines changed

_includes/author-card.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<!-- this looks weird, but it was the only way I could figure out how to test
1111
that the first character of include.author is "@" -->
1212
{% if at_sign.size > 0 and test_at_sign_is_first == 3 %}
13-
<div data-card-user="{{author_no_at_sign}}" class="{{ extra_classes }}"></div><br/>
13+
<div class="user-card {{ extra_classes }}" data-card-user="{{author_no_at_sign}}"><div class="user-card-name">{{ include.author }}</div></div></br>
1414
{% else %}
1515
<div class="user-card {{ extra_classes }}"><div class="user-card-name">{{ include.author }}</div></div>
1616
{% endif %}

_includes/head.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
<script src="/javascripts/respond.js"></script>
3636
<script src="/javascripts/ua-parser.min.js"></script>
3737

38+
<!-- Caches data from requests to API endpoints -->
39+
<script src="/javascripts/api-cache.js"></script>
3840
<!-- Loads user-cards from GH API -->
3941
<script src="/javascripts/cards.js"></script>
4042
<!-- Configures special anchors to link to the most recent ev3dev release -->

docs/getting-started.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ please [open an issue on GitHub](/support){: .alert-link}.
5050

5151
## Step 1: Download the latest ev3dev image file
5252

53+
<div class="release-link-container" markdown="1">
54+
5355
<br/>
5456
<div class="text-center">
5557
<a data-release-link-platform="ev3" class="btn btn-lg btn-primary"><span class="glyphicon glyphicon-download-alt"></span> Download for LEGO MINDSTORMS EV3</a>
@@ -65,6 +67,17 @@ To get started, you will need to download the release corresponding to the platf
6567
you are using. If you are looking for older releases or other file types, you can
6668
check out the [GitHub releases page][releases].
6769

70+
</div>
71+
<div class="release-link-alt" markdown="1">
72+
To get started, you will need to download the release corresponding to the platform
73+
you are using. Visit the [GitHub releases page][releases] and find the image that
74+
corresponds to your platform:
75+
76+
- Releases for the LEGO MINDSTORMS EV3 start with `ev3- `
77+
- Releases for the Raspberry Pi 1 start with `rpi-`
78+
- Releases for the Raspberry Pi 2 and 3 start with `rpi2-`
79+
- Releases for the BeagleBone start with `evb-`
80+
</div>
6881
</div>
6982
</div>
7083

javascripts/api-cache.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
var cacheKey = "api-cache";
2+
3+
function supportsHtml5Storage() {
4+
try {
5+
return 'localStorage' in window && window['localStorage'] !== null;
6+
} catch (e) {
7+
return false;
8+
}
9+
}
10+
11+
function getApiValue(endpointUrl, cacheTime, callback) {
12+
try {
13+
var cacheData = supportsHtml5Storage() ? JSON.parse(localStorage[cacheKey]) : null;
14+
// This does an exact match for the URL given. Different spacing, duplicate slashes,
15+
// alternate caps, etc. will result in the cache being bypassed.
16+
if (cacheData && cacheData[endpointUrl] && Date.now() - cacheData[endpointUrl].dateRetrieved < cacheTime) {
17+
callback(cacheData[endpointUrl].requestResult);
18+
return;
19+
}
20+
}
21+
catch (e) {
22+
// Ignore the error; if the saved JSON is invalid, we'll just request it from the server.
23+
}
24+
25+
console.log('No cached copy of API data for endpoint "' + endpointUrl + '" found. Downloading from remote server.');
26+
$.ajax(endpointUrl).done(function (apiData) {
27+
if (supportsHtml5Storage()) {
28+
var cacheData = {};
29+
30+
try {
31+
cacheData = JSON.parse(localStorage[cacheKey]);
32+
}
33+
catch(e) {
34+
// Ignore error; either the cache doesn't exist or it is invalid
35+
}
36+
37+
cacheData[endpointUrl] = {
38+
dateRetrieved: Date.now(),
39+
requestResult: apiData
40+
};
41+
42+
localStorage.setItem(cacheKey, JSON.stringify(cacheData));
43+
}
44+
45+
callback(apiData);
46+
}).fail(function (xhr, error) {
47+
console.error("Error getting API data: " + error);
48+
callback(null, error);
49+
});
50+
}

javascripts/cards.js

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,33 @@
1+
// Cache will time out after 1 hour
2+
var userCardCacheTimeMillis = 60 * 60 * 1000;
3+
14
$(document).ready(function () {
25
$('div[data-card-user]').each(function (i, element) {
36
var $cardDiv = $(element);
4-
$.ajax('https://api.github.com/users/' + $cardDiv.data('card-user')).done(function (userData) {
5-
$cardDiv.addClass('user-card');
6-
$cardDiv.append('<a href="' + userData.html_url + '"></a>');
7-
$cardDiv = $cardDiv.children(':last-child')
8-
9-
$cardDiv.append('<div class="user-card-avatar"><img src="' + userData.avatar_url + '"/></div>');
10-
var $avatarDiv = $cardDiv.children(':last-child');
11-
$cardDiv.append('<div class="user-card-text"></div>');
12-
var $textDiv = $cardDiv.children(':last-child');
7+
getApiValue('https://api.github.com/users/' + $cardDiv.data('card-user'), userCardCacheTimeMillis, function (userData, error) {
8+
if (error || !userData) {
9+
console.error("User card data not available! Using static text card instead.");
10+
return;
11+
}
1312

13+
$cardDiv.empty();
14+
$cardDiv = $('<a href="' + userData.html_url + '"></a>').appendTo($cardDiv);
15+
16+
var $avatarDiv = $('<div class="user-card-avatar"><img src="' + userData.avatar_url + '"/></div>').appendTo($cardDiv);
17+
var $textDiv = $('<div class="user-card-text"></div>').appendTo($cardDiv);
18+
1419
$textDiv.append('<div class="user-card-name">' + (userData.name || userData.login) + '</div>');
1520
$textDiv.append('<div class="user-card-login">@' + userData.login + '</div>');
16-
21+
1722
// Give the text a margin to make sure it does not overlap the avatar
18-
$avatarDiv.resize(function() {
23+
$avatarDiv.resize(function () {
1924
$textDiv.css({
2025
width: 'auto',
2126
"margin-left": $avatarDiv.height()
2227
});
2328
});
24-
29+
2530
$avatarDiv.resize();
26-
});
31+
});
2732
});
2833
});

javascripts/releases.js

Lines changed: 43 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
var ev3devRepoReleaseCacheKey = 'ev3dev-repo-release-cache';
21
// Cache will time out after 20 minutes
32
var releaseCacheTimeMillis = 20 * 60 * 1000;
43

@@ -9,71 +8,55 @@ var releasePlatformRegexes = {
98
evb: "evb-[\\w\\d-]*\\.img\\.xz",
109
}
1110

12-
function supportsHtml5Storage() {
13-
try {
14-
return 'localStorage' in window && window['localStorage'] !== null;
15-
} catch (e) {
16-
return false;
17-
}
18-
}
11+
function initDownloadLinks() {
12+
getApiValue('https://api.github.com/repos/ev3dev/ev3dev/releases', releaseCacheTimeMillis, function (releases, error) {
13+
if(error) {
14+
console.error("Download links not available! Falling back to static content.");
15+
$('.release-link-container').hide();
16+
$('.release-link-alt').show();
17+
18+
return;
19+
}
20+
21+
releases.sort(function (a, b) {
22+
if (Date.parse(a['created_at']) < Date.parse(b['created_at']))
23+
return 1;
24+
if (Date.parse(a['created_at']) > Date.parse(b['created_at']))
25+
return -1;
1926

20-
$(document).ready(function () {
21-
if ($('a[data-release-link-platform]').length > 0) {
22-
function initReleaseLinks(releases) {
23-
releases.sort(function (a, b) {
24-
if (Date.parse(a['created_at']) < Date.parse(b['created_at']))
25-
return 1;
26-
if (Date.parse(a['created_at']) > Date.parse(b['created_at']))
27-
return -1;
27+
return 0;
28+
});
2829

29-
return 0;
30-
});
30+
$('a[data-release-link-platform]').each(function (i, element) {
31+
var $linkElem = $(element);
32+
var targetReleasePlatform = $linkElem.data('release-link-platform');
33+
if (!releasePlatformRegexes[targetReleasePlatform]) {
34+
console.error('"' + targetReleasePlatform + '" is an invalid release target.');
35+
return true;
36+
}
3137

32-
$('a[data-release-link-platform]').each(function (i, element) {
33-
var $linkElem = $(element);
34-
var targetReleasePlatform = $linkElem.data('release-link-platform');
35-
if(!releasePlatformRegexes[targetReleasePlatform]) {
36-
console.error('"' + targetReleasePlatform + '" is an invalid release target.');
37-
return true;
38-
}
39-
40-
var platformRegex = new RegExp(releasePlatformRegexes[targetReleasePlatform]);
38+
var platformRegex = new RegExp(releasePlatformRegexes[targetReleasePlatform]);
4139

42-
for(var releaseIndex in releases) {
43-
var releaseAssets = releases[releaseIndex].assets;
44-
for(var assetIndex in releaseAssets) {
45-
if(platformRegex.test(releaseAssets[assetIndex].name)) {
46-
$linkElem.attr('href', releaseAssets[assetIndex]['browser_download_url']);
47-
return true;
48-
}
40+
for (var releaseIndex in releases) {
41+
var releaseAssets = releases[releaseIndex].assets;
42+
for (var assetIndex in releaseAssets) {
43+
if (platformRegex.test(releaseAssets[assetIndex].name)) {
44+
$linkElem.attr('href', releaseAssets[assetIndex]['browser_download_url']);
45+
return true;
4946
}
5047
}
51-
});
52-
}
48+
}
49+
});
50+
});
51+
}
5352

54-
var cacheData;
55-
try {
56-
cacheData = supportsHtml5Storage() ? JSON.parse(localStorage.getItem(ev3devRepoReleaseCacheKey)) : null;
57-
}
58-
catch(e) {
59-
// Ignore the error; if the saved JSON is invalid, we'll just request it from the server.
60-
}
61-
62-
if (cacheData && Date.now() - cacheData.dateRetrieved < releaseCacheTimeMillis) {
63-
initReleaseLinks(cacheData.releaseData);
64-
}
65-
else {
66-
console.log("No cached copy of releases found. Downloading from GitHub.");
67-
$.ajax('https://api.github.com/repos/ev3dev/ev3dev/releases').done(function (releases) {
68-
if (supportsHtml5Storage())
69-
localStorage.setItem(ev3devRepoReleaseCacheKey, JSON.stringify({
70-
dateRetrieved: Date.now(),
71-
releaseData: releases
72-
}));
73-
initReleaseLinks(releases);
74-
}).fail(function (error) {
75-
console.error("Error getting release info: " + error);
76-
});
77-
}
53+
$(document).ready(function () {
54+
// If JS is disabled, this code will never run and the alt content will be left as-is.
55+
// We do this as soon as the document loads so that the page flash is minimal.
56+
$('.release-link-alt').hide();
57+
$('.release-link-container').show();
58+
59+
if ($('a[data-release-link-platform]').length > 0) {
60+
initDownloadLinks();
7861
}
7962
});

stylesheets/page-content.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,8 @@
3636

3737
.colored-section {
3838
padding-bottom: 10.5px;
39+
}
40+
41+
.release-link-container {
42+
display: none;
3943
}

0 commit comments

Comments
 (0)