0

I'm creating an HTML5 2D game and I want to request each asset only once and then store them in the user's filesystem, I'm using localStorage for this task, however AFAIK it has a limit of 5mb per origin, (my whole game will have more than that), and I want to know how to store my game assets in the user's machine without that limitation, this is what I've done until now:

items.js:

/**
 * Copyright 2014 - Edgar Alexander Franco.
 *
 * @author Edgar Alexander Franco
 * @version 1.0.0
 */

var items = [
  {
    name : 'characters_scott', 
    url : './img/game/characters/scott', 
    type : 'png'
  }, 
  {
    name : 'map_1', 
    url : './img/game/map/1', 
    type : 'jpg'
  }, 
  {
    name : 'map_2', 
    url : './img/game/map/2', 
    type : 'jpg'
  }
];

Resource.js

/**
 * Copyright 2014 - Edgar Alexander Franco.
 *
 * @author Edgar Alexander Franco
 * @version 1.0.0
 */

var Resource = (function () {
  var self = {};

  self.get = {};

  self.load = function (items) {
    var xhr = (typeof XMLHttpRequest != 'undefined') ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP') ;
    var item, content, mime;

    for (var i in items) {
      item = items[ i ];
      content = localStorage.getItem(item.url);

      if (content == null) {
        xhr.open('GET', item.url, false);
        xhr.send();
        content = xhr.responseText;
        localStorage.setItem(item.url, content);
      }

      if (item.type != 'audio') {
        mime = (item.type == 'jpg') ? 'image/jpeg' : 'image/png' ;
        self.get[ item.name ] = new Image();
        self.get[ item.name ].src = 'data:' + mime + ';base64,' + content;
      } else {
        // Not yet...
      }
    }
  }

  return self;
})();

The code from above works great, but doesn't cover my needs, as you can see I'm using localStore and it has it's limitations, I want to adapt the same code but for an unlimited storage, any ideas?

Edgar Alexander
  • 364
  • 4
  • 11

2 Answers2

1

I'd suggest you use localForage by Mozilla[1]. It provides the same simple API as localStorage, but is backed by IndexedDB (with a WebSQL / localStorage fallback I think).

An alternative, if the asset URLs are the same for all players, you can use applicationCache. I'd recommend you to use an appCache wrapper library like appCacheNanny[2] (Disclaimer: I created that.)

[1] https://github.com/mozilla/localForage
[2] https://github.com/gr2m/appcache-nanny

Gregor
  • 2,325
  • 17
  • 27
0

After a long research I concluded that IndexedDB is probably the most capable local storage we can find for this task, so, I re-designed my code and now this is the full class:

/**
 * Copyright 2014 - Edgar Alexander Franco.
 *
 * @author Edgar Alexander Franco
 * @version 1.0.0
 */

var Resources = (function () {
  var self = {};

  self.get = {};

  self.DOWNLOADED = 1;
  self.LOADED = 2;

  var DB_NAME = 'evilition';
  var TABLE_NAME = 'resources';

  self.audioType = (document.createElement('audio').canPlayType('audio/mp3') == '') ? '.ogg' : '.mp3' ;

  /**
   * Load the assets from the server / filesystem depending if each is cached or not.
   *
   * @param {object} resources Resources of the game.
   * @param {function} callback1 Function to be called on progress.
   * @param {function} callback2 Function to be called on error.
   * @param {function} callback3 Function to be called once the resources are loaded.
   */
  self.load = function (resources, callback1, callback2, callback3) {
    var request = indexedDB.open(DB_NAME, 3);

    request.onerror = function (evt) {
      callback2({});
    }

    request.onupgradeneeded = function (evt) {
      var db = evt.target.result;
      var table = db.createObjectStore(TABLE_NAME, {
        keyPath : 'name'
      });
      table.createIndex('name', 'name', {
        unique : true
      });
    }

    request.onsuccess = function (evt) {
      function loadResource () {
        var resource = resources[ i ];
        var request = objectStore.get(resource.name);

        request.onerror = function (evt) {
          callback2(resource);
        }

        request.onsuccess = function (evt) {
          var progress = Math.round(((i + 1) * 100) / total);
          var content;

          if (typeof request.result == 'undefined') {
            if (resource.type == 'audio') {
              resource.path += self.audioType;
            }

            xhr.open('GET', resource.path + '.b64', false);
            xhr.send();

            if (xhr.readyState == 4 && xhr.status == 200) {
              content = xhr.responseText;
              objectStore.add({
                name : resource.name, 
                content : content
              });
              callback1(resource, progress, self.DOWNLOADED);
            } else {
              callback2(resource);

              return;
            }
          } else {
            content = request.result.content;
            callback1(resource, progress, self.LOADED);
          }

          var mime;

          if (resource.type != 'audio') {
            mime = (resource.type == 'jpg') ? 'image/jpeg' : 'image/png' ;
            self.get[ resource.name ] = new Image;
          } else {
            mime = (self.audioType == '.mp3') ? 'audio/mp3' : 'audio/ogg' ;
            self.get[ resource.name ] = new Audio;
          }

          self.get[ resource.name ].src = 'data:' + mime + ';base64,' + content;
          i++;

          if (i == total) {
            db.close();
            callback3();
          } else {
            loadResource();
          }
        }
      }

      var db = evt.target.result;
      var objectStore = db.transaction(TABLE_NAME, 'readwrite').objectStore(TABLE_NAME);
      var xhr = (typeof XMLHttpRequest != 'undefined') ? new XMLHttpRequest : new ActiveXObject('Microsoft.XMLHTTP') ;
      var total = resources.length;
      var i = 0;

      loadResource();
    }
  }

  /**
   * Delete the resources database requiring the creation of a new one in the next load.
   */
  self.clearLocalCache = function () {
    indexedDB.deleteDatabase(DB_NAME);
  }

  return self;
})();

Thank you Gregor for you recommendation :)

Edgar Alexander
  • 364
  • 4
  • 11