Archives de Tag: mylife

Html5 offline + Rails : épisode 1 Monsieur Manifest

Dans le cadre de mon boulot diurne, je développe en ce moment une application web pour iPad. Cette dernière doit pouvoir continuer à fonctionner sans connexions, c’est à dire passer en mode déconnecté.

Notre choix s’est porté sur les technologies offerte par l’HTML5, pour une raison évidente : Steve Jobs aime l’HTML5. Donc les utilisateurs de la tablette à la pomme aime l’HTML5. Ne cherchez pas Steve a raison.

Je ne vais pas vous donner un cours, des liens vers des spécs, mais plus vous donner du code que j’ai mis en place pour démarrer le projet. C’est loin d’être parfait et tant qu’à faire toute amélioration (avec du code, le blabla j’m’en scotche) est vivement encouragée.

Sinon je vous recommande http://www.html5rocks.org, c’est une bonne ressource pour débuter avec les notions d’appli web offline.

Bref, pour que notre application fonctionne, nous devons stocker en local, l’ensemble des fichiers dont elle aura besoin pour avoir une tronche acceptable.

Ce qu’il faut savoir :

  • dès que le manifest change d’un octet il est considéré comme modifié et donc l’API d’applicationCache permettra de lancer la mise à jour
  • le manifest doit être servit au format manifest et surtout pas du text
  • utiliser un FALLBACK permet d’éviter à l’API JS de péter lors de la récupérations des fichiers. En cas réels avec un ensemble d’assets important, il est dommage de bloquer la synchronisation pour un fichier.
  • le manifest permet de définir des zones où la connexion est obligatoire, celà évite de se faire chier à gérer le cas de l’authentification partout
  • le chemin vers le manifest de la page doit être ajouté dans la balise html

Chez Novelys, nous utilisons Ruby on rails pour la partie serveur de notre application, nous avons rajouté le mime type manifest, afin d’avoir une API Rest qui sache servir ce nouveau format.

Le code suivant dans un initializer fait bien le boulot :

Mime::Type.register_alias "text/cache-manifest", :manifest

Bien, à ce stade, il faut générer notre Manifest.

Dans une applications Rails, nous allons devoir coller dans l’applicationCache : les feuilles de styles, les lib JS, les assets et suivant les cas les fichiers uploadés.

Si un fichier est modifié, il faudra impérativement que le manifest soit modifié, pour que ce fichier soit par la suite téléchargé en local.

Nous avons donc opté pour intégrer dans un commentaire une date formatée, celle de la dernière modification opérée sur les fichiers du manifest. Ainsi on est sûr que le manifest soit bien considéré comme mis à jour et que les fichiers soit téléchargé par les iPads. (nous sommes riches).

Voici le helper qui aidera à accomplir la partie chiante :

def manifest_files(options = {})
    timestamp_format = "%Y%m%d%H%M%S"

    #Assets
    assets = options[:assets] || []
    max_time = options[:max_time] || DateTime.now

    # Interface
    stylesheets = Dir["public/stylesheets/**/*.*"].map{|entry| entry.gsub(/public/, "") }
    javascripts = Dir["public/javascripts/**/*.*"].map{|entry| entry.gsub(/public/, "") }
    images      = Dir["public/images/**/*.*"].map{|entry| entry.gsub(/public/, "") }
    interface = stylesheets + javascripts + images

    if interface.present?
      max_mtime = interface.map{|file| File.mtime("public"+file)}.max
      max_time = [max_time, max_mtime].max
    end

    max_mtime = max_time.strftime(timestamp_format)

    manifest_files = (assets + interface).flatten.uniq
    return manifest_files, max_mtime
  end

Le helper exploite deux options :

  1. max_time : un timestamp arbitraire qui sera tout de même comparés au plus récent des fichiers
  2. une liste des chemins des chiers à rajouter dans le manifest (typiquement les fichiers que vous gérer avec Paperclip dans vos models).

Et on récupère :

  1. la liste des fichiers
  2. la date formatée avec le timestamp le plus récent, c’est à dire la version de note Manifest

Une actions :

def mister_manifest
    @manifest_files, @max_mtime = manifest_files()

    respond_to do |format|
      format.manifest
    end
  end

Une vue ERB:

CACHE MANIFEST
# version <%= @max_mtime %>
/
<%= @manifest_files.join("\n") %>
FALLBACK:/ /offline.html

Le manifest est maintenant généré, reste maintenant à l’intégrer dans l’application côté client.

Primo rajoutons le chemin vers le manifest, voici un exemple dans notre layout général :

!!!
%html{:manifest => application_offline_manifest_path(:format => :manifest) }
  %head
    %title MyApp
    != stylesheet_link_tag %w(sensassional)
    != javascript_include_tag %w(jquery sensassional application_cache)
    = csrf_meta_tag
  %body
    = yield

Là tout doux, maintenant il faut taper dans les API Javascripts pour synchroniser le cache en locale. Voici le code que j’ai rapidement mise en oeuvre. Rassurez vous c’est une solution temporaire (permanente) :

var req=0;
var manifest_size = 0;

var cacheStatusValues = [];
cacheStatusValues[0] = 'uncached';
cacheStatusValues[1] = 'idle';
cacheStatusValues[2] = 'checking';
cacheStatusValues[3] = 'downloading';
cacheStatusValues[4] = 'updateready';
cacheStatusValues[5] = 'obsolete';

var cache = window.applicationCache;

console.log(window);

function logEvent(e) {
  var online, status, type, message;
  req ++;
  online = (navigator.onLine) ? 'yes' : 'no';
  status = cacheStatusValues[cache.status];
  type = e.type;
  message = '' + type;
  message+= '/' + status;
  message+= '/' + req  + '/' + manifest_size ;

  if (type == 'error' && navigator.onLine) {
      message+= ' (probably a syntax error in manifest)';
      console.log(message);
      console.dir(e);
  }

}

cache.addEventListener('loadstart', logEvent, false);
cache.addEventListener('stalled', logEvent, false);
cache.addEventListener('load', logEvent, false);
cache.addEventListener('loadend', logEvent, false);
cache.addEventListener('cached', logEvent, false);
cache.addEventListener('checking', logEvent, false);
cache.addEventListener('downloading', logEvent, false);
cache.addEventListener('error', logEvent, false);
cache.addEventListener('noupdate', logEvent, false);
cache.addEventListener('obsolete', logEvent, false);
cache.addEventListener('progress', logEvent, false);
cache.addEventListener('updateready', logEvent, false);

Nous devrons sans doute encore l’enrichir, mais ça sera pour un prochain billet 🙂

Maintenant débranchez votre RJ45 ou votre connexion Wifi et magie !

Vous allez maintenant pouvoir frimer avec votre iPad sans connexion 3G.

Le prochain épisode : Backbone.js et localStorage sont dans un bateau.

Publicité

Liste de courses ædifico

Je gère quelques serveurs pour arrondir de quelques deniers mes fins de mois en tant qu’auto entrepreneur.

Et j’aimerai automatiser un certains nombres de tâches :

  • parce qu’elles sont chiantes et pas très fréquentes
  • parce qu’elles sont chiantes et fréquentes
  • parce que j’aimerai améliorer mon suivi
  • parce que ça m’emmerde de refaire plus de 3 fois la même manipulation
  • parce que j’aimerai passer moins de temps à certaines tâches pour  faire d’autres bidouille intellectuellement plus stimulante
  • parce que j’ai envie d’avoir plus de clients et donc plus d’€uros pour être plus h€ur€ux.

J’ai testé plusieurs outils de « provisionning » comme chef ou puppet. Mais il sont overkill pour mon besoin. Je passe plus de temps à ecrire/tester les recettes de déploiement qu’à les utiliser.

Les infras que j’ai en gestion sont souvent des machines solo ou en couple et hébergeant des applications radicalement différentes (crm, e-shop, platefome de dév ou encore trousse à outil numérique…), donc j’aimerai quelquechose qui me permettent de :

  • facilement décrire un setup de base (hors sytème initiale) pour standardiser les bousins
  • facilement d’installer un truc courant
  • facilement créer une ressource (user, vhost, base de données) et tout ce que ça implique (backup et tout le toutim)
  • rester simple
  • répondre à un besoin modeste et pas un cluster de 44millions de cores

Bien, j’ai donc exprimé à la face du monde mon poney, mais maintenant je suis face à la page blanche et le spleen m’emvahis : Par où je commence ?

Je vais donc débuter par une liste des trucs de base dont j’ai besoin :

  1. un sobriquet, histoire de faire un peu de marketing. Ça sera aedifico et basta.
  2. un compte github ou bitbucket, va falloir se décider.
  3. un virtualenv python 2.6
  4. un template pour la doc reST/python-sphinx
  5. un trac histoire de m’organiser et définir mon plan (machiavelique)
  6. un blog en guise de Moleskine numérique, ça sera ce blog tant qu’à faire…
  7. de l’huile de coude 🙂