From c2ace7695c589ba48923f645a9d8f9957c54dbb4 Mon Sep 17 00:00:00 2001 From: Hannes Mannerheim Date: Thu, 5 Mar 2015 22:22:48 +0100 Subject: [PATCH] better localStorage cache management --- js/ajax-functions.js | 20 +++--- js/dom-functions.js | 29 ++++----- js/misc-functions.js | 148 +++++++++++++++++++++++++++++++++++++++---- js/qvitter.js | 81 ++++++++++++++--------- 4 files changed, 206 insertions(+), 72 deletions(-) diff --git a/js/ajax-functions.js b/js/ajax-functions.js index 162eb7e..2bc3435 100644 --- a/js/ajax-functions.js +++ b/js/ajax-functions.js @@ -365,18 +365,19 @@ function unRequeet(this_stream_item, this_action, my_rq_id) { function getFavsAndRequeetsForQueet(q,qid) { // get immediately from localstorage cache - if(localStorageIsEnabled()) { - if(typeof localStorage['favsAndRequeets-' + qid] != 'undefined' && localStorage['favsAndRequeets-' + qid] !== null) { - showFavsAndRequeetsInQueet(q, JSON.parse(localStorage['favsAndRequeets-' + qid])); - } - } + localStorageObjectCache_GET('favsAndRequeets',qid,function(data){ + if(data) { + showFavsAndRequeetsInQueet(q, data); + } + }); $.ajax({ url: window.apiRoot + "qvitter/favs_and_repeats/" + qid + ".json?t=" + timeNow(), type: "GET", dataType: 'json', success: function(data) { if(data.favs.length > 0 || data.repeats.length > 0) { - if(localStorageIsEnabled()) { localStorage['favsAndRequeets-' + qid] = JSON.stringify(data);} // cache response + + localStorageObjectCache_STORE('favsAndRequeets',qid, data); // cache response if(q.hasClass('expanded') && !q.hasClass('collapsing')) { showFavsAndRequeetsInQueet(q,data); @@ -384,11 +385,7 @@ function getFavsAndRequeetsForQueet(q,qid) { } else { // remove from cache and DOM if all favs and repeats are deleted - if(localStorageIsEnabled()) { - if(typeof localStorage['favsAndRequeets-' + qid] != 'undefined' && localStorage['favsAndRequeets-' + qid] !== null) { - delete localStorage['favsAndRequeets-' + qid]; - } - } + localStorageObjectCache_STORE('favsAndRequeets',qid, false); q.children('.queet').find('.stats').remove(); } }, @@ -398,3 +395,4 @@ function getFavsAndRequeetsForQueet(q,qid) { } }); } +showFavsAndRequeetsInQueet \ No newline at end of file diff --git a/js/dom-functions.js b/js/dom-functions.js index a201a65..705a59f 100644 --- a/js/dom-functions.js +++ b/js/dom-functions.js @@ -1215,29 +1215,26 @@ function convertNewGNUSocialURItoURL(obj) { function getConversation(q, qid) { // check if we have a conversation for this notice cached in localstorage - if(localStorageIsEnabled()) { - - if(typeof localStorage['conversation-' + qid] != 'undefined' && localStorage['conversation-' + qid] !== null) { - showConversation(q, qid, JSON.parse(localStorage['conversation-' + qid])); - } + localStorageObjectCache_GET('conversation',qid, function(data){ + if(data) { + showConversation(q, qid, data); + } // if we have a conversation for the notice that this notice is a reply to, // we assume it's the same conversation, and use that - else if(q.attr('data-in-reply-to-status-id') !== null - && q.attr('data-in-reply-to-status-id') != 'null' - && typeof localStorage['conversation-' + q.attr('data-in-reply-to-status-id')] != 'undefined' - && localStorage['conversation-' + q.attr('data-in-reply-to-status-id')] !== null) { - showConversation(q, qid, JSON.parse(localStorage['conversation-' + q.attr('data-in-reply-to-status-id')])); - } - - } + else if(q.attr('data-in-reply-to-status-id') !== null && q.attr('data-in-reply-to-status-id') != 'null') { + localStorageObjectCache_GET('conversation',q.attr('data-in-reply-to-status-id'), function(data){ + if(data) { + showConversation(q, qid, data); + } + }); + } + }); // always get most recent conversation from server getFromAPI('statusnet/conversation/' + $('#stream-item-' + qid).attr('data-conversation-id') + '.json?count=100', function(data){ if(data) { // cache in localstorage - if(localStorageIsEnabled()) { - localStorage['conversation-' + qid] = JSON.stringify(data); - } + localStorageObjectCache_STORE('conversation',qid, data); showConversation(q, qid,data); }}); diff --git a/js/misc-functions.js b/js/misc-functions.js index cbffde2..ffd2548 100644 --- a/js/misc-functions.js +++ b/js/misc-functions.js @@ -55,11 +55,67 @@ function localStorageObjectCache_STORE(name, unique_id, object) { } } else { - localStorage[name + '-' + unique_id] = JSON.stringify(object); + + var dataToSave = {}; + dataToSave.modified = Date.now(); + dataToSave.data = object; + + try { + localStorage.setItem(name + '-' + unique_id, JSON.stringify(dataToSave)); + } + catch (e) { + if (e.name == 'QUOTA_EXCEEDED_ERR' || e.name == 'NS_ERROR_DOM_QUOTA_REACHED' || e.name == 'QuotaExceededError' || e.name == 'W3CException_DOM_QUOTA_EXCEEDED_ERR') { + + removeOldestLocalStorageEntries(function(){ + localStorageObjectCache_STORE(name, unique_id, object); + }); + + } + else { + console.log('could not store in localStorage, unknown error'); + } + } } } } +/* · + · + · Remove the 100 oldest cached items + · + · · · · · · · · · */ + +function removeOldestLocalStorageEntries(callback) { + + // grab the expiry and store the modified-key into an object + var modified = Object.keys(localStorage).reduce(function(collection,key){ + var currentModified = JSON.parse(localStorage.getItem(key)).modified; + collection[currentModified] = key; + return collection; + },{}); + + delete modified['undefined']; // we don't want those + + // get the modified dates into an array + var modifiedDates = Object.keys(modified); + + modifiedDates.sort(); + + var i = 0; + $.each(modifiedDates,function(k,v){ + delete localStorage[modified[v]]; + i++; + if(i>=100) { + return false; + } + }); + + console.log('removed 100 old localstorage items'); + + callback(); + } + + /* · · · Get from localStorage object cache @@ -74,15 +130,80 @@ function localStorageObjectCache_GET(name, unique_id, callback) { if(localStorageIsEnabled()) { if(typeof localStorage[name + '-' + unique_id] != 'undefined' && localStorage[name + '-' + unique_id] !== null) { - callback(JSON.parse(localStorage[name + '-' + unique_id])); + var parsedObject = JSON.parse(localStorage[name + '-' + unique_id]); + if(typeof parsedObject.modified == 'undefined' || parsedObject.modified === null) { + // invalid or old localstorage object found, check the whole localstorage! + checkLocalStorage(); + callback(false); + } + else { + callback(parsedObject.data); + } } else { callback(false); } + } + else { + callback(false); } } +function checkLocalStorage() { + console.log('checking localStorage for invalid entries'); + var dateNow = Date.now() + var corrected = 0; + var deleted = 0; + $.each(localStorage, function(k,entry){ + if(typeof entry == 'string') { + // check that entry is valid json + try { + var entryParsed = JSON.parse(entry); + } + catch(e) { + delete localStorage[k]; + deleted++; + return true; + } + + // check that it is a valid/currently used data type + var validDataTypes = [ + 'browsingHistory', + 'conversation', + 'favsAndRequeets', + 'languageData', + 'fullQueetHtml', + 'selectedLanguage' + ]; + var thisDataType = k.substring(0,k.indexOf('-')); + if($.inArray(thisDataType, validDataTypes) == -1 || k.indexOf('-') == -1) { + delete localStorage[k]; + deleted++; + return true; + } + + // check that it has a modified entry, if not: add one + if(typeof entryParsed.modified == 'undefined' || entryParsed.modified === null) { + var newEntry = {}; + newEntry.modified = dateNow - corrected; // we want as unique dates as possible + newEntry.data = entryParsed; + try { + localStorage.setItem(k, JSON.stringify(newEntry)); + } + catch (e) { + if (e.name == 'QUOTA_EXCEEDED_ERR' || e.name == 'NS_ERROR_DOM_QUOTA_REACHED' || e.name == 'QuotaExceededError' || e.name == 'W3CException_DOM_QUOTA_EXCEEDED_ERR') { + removeOldestLocalStorageEntries(function(){ + localStorage.setItem(k, JSON.stringify(newEntry)); + }); + } + } + corrected++; + } + } + }); + console.log(corrected + ' entries corrected, ' + deleted + ' entries deleted'); + } /* · · @@ -620,14 +741,13 @@ function convertAttachmentMoreHref() { /* · · - · Updates the local storage + · Updates the browsing history local storage · · · · · · · · · · · · · · */ function updateHistoryLocalStorage() { if(localStorageIsEnabled()) { var i=0; - var localStorageName = window.loggedIn.screen_name + '-history-container-v2'; var historyContainer = new Object(); $.each($('#history-container .stream-selection'), function(key,obj) { historyContainer[i] = new Object(); @@ -635,7 +755,7 @@ function updateHistoryLocalStorage() { historyContainer[i].dataStreamHeader = $(obj).attr('data-stream-header'); i++; }); - localStorage[localStorageName] = JSON.stringify(historyContainer); + localStorageObjectCache_STORE('browsingHistory', window.loggedIn.screen_name,historyContainer); if($('#history-container .stream-selection').length==0) { $('#history-container').css('display','none'); } @@ -656,15 +776,15 @@ function updateHistoryLocalStorage() { function loadHistoryFromLocalStorage() { if(localStorageIsEnabled()) { - var localStorageName = window.loggedIn.screen_name + '-history-container-v2'; - if(typeof localStorage[localStorageName] != "undefined") { - $('#history-container').css('display','block'); - $('#history-container').html(''); - var historyContainer = $.parseJSON(localStorage[localStorageName]); - $.each(historyContainer, function(key,obj) { - $('#history-container').append('' + obj.dataStreamHeader + ''); - }); - } + localStorageObjectCache_GET('browsingHistory', window.loggedIn.screen_name,function(data){ + if(data) { + $('#history-container').css('display','block'); + $('#history-container').html(''); + $.each(data, function(key,obj) { + $('#history-container').append('' + obj.dataStreamHeader + ''); + }); + } + }); updateHistoryLocalStorage(); } } diff --git a/js/qvitter.js b/js/qvitter.js index 4b6e625..3541607 100644 --- a/js/qvitter.js +++ b/js/qvitter.js @@ -36,6 +36,9 @@ // object to keep old states of streams in, to speed up stream change window.oldStreams = new Object(); +// check our localStorage and make sure it's correct +checkLocalStorage(); + /* · · @@ -270,15 +273,27 @@ $(window).load(function() { } window.selectedLanguage = 'en'; - - if(localStorageIsEnabled()) { - if(typeof localStorage.selectedLanguage != 'undefined' && localStorage.selectedLanguage != null) { - window.selectedLanguage = localStorage.selectedLanguage; - } - else if(typeof window.availableLanguages[browserLang] != 'undefined') { - window.selectedLanguage = browserLang; - } + + if(window.loggedIn === false) { + var selectedForUser = 'logged_out'; } + else { + var selectedForUser = window.loggedIn.id; + } + + localStorageObjectCache_GET('selectedLanguage',selectedForUser, function(data){ + if(data) { + window.selectedLanguage = data; + } + else { + window.selectedLanguage = browserLang; + } + }); + + // check that this language is available, otherwise use english + if(typeof window.availableLanguages[window.selectedLanguage] == 'undefined') { + window.selectedLanguage = 'en'; + } // if this is a RTL-language, add rt classes and change some things if(window.selectedLanguage == 'ar') { @@ -289,31 +304,28 @@ $(window).load(function() { $('body').addClass('rtl'); $('title').html('‫واگذارنده'); } - + // if we already have this version of this language in localstorage, we // use that cached version. we do this because $.ajax doesn't respect caching, it seems - if(localStorageIsEnabled() - && (typeof localStorage['languageData-' + window.availableLanguages[window.selectedLanguage]] != 'undefined' && localStorage['languageData-' + window.availableLanguages[window.selectedLanguage]] !== null)) { - proceedToSetLanguageAndLogin(JSON.parse(localStorage['languageData-' + window.availableLanguages[window.selectedLanguage]])); - } - // if we need to get the language file from the server - else { - $.ajax({ - dataType: "json", - url: window.fullUrlToThisQvitterApp + 'locale/' + window.availableLanguages[window.selectedLanguage], - error: function(data){console.log(data)}, - success: function(data) { + localStorageObjectCache_GET('languageData',window.availableLanguages[window.selectedLanguage], function(data){ + if(data) { + proceedToSetLanguageAndLogin(data); + } + else { + $.ajax({ + dataType: "json", + url: window.fullUrlToThisQvitterApp + 'locale/' + window.availableLanguages[window.selectedLanguage], + error: function(data){console.log(data)}, + success: function(data) { - // store this response in localstorage - if(localStorageIsEnabled()) { - localStorage['languageData-' + window.availableLanguages[window.selectedLanguage]] = JSON.stringify(data); + // store this response in localstorage + localStorageObjectCache_STORE('languageData',window.availableLanguages[window.selectedLanguage], data); + + proceedToSetLanguageAndLogin(data); } - - proceedToSetLanguageAndLogin(data); - } - }); - - } + }); + } + }); }); @@ -605,9 +617,16 @@ $(document).bind('click', function (e) { } }); $('.language-link').click(function(){ - if(localStorageIsEnabled()) { - localStorage.selectedLanguage = $(this).attr('data-lang-code'); // save langage selection + + if(window.loggedIn === false) { + var selectedForUser = 'logged_out'; } + else { + var selectedForUser = window.loggedIn.id; + } + + localStorageObjectCache_STORE('selectedLanguage',selectedForUser, $(this).attr('data-lang-code')); + location.reload(); // reload });