Blog d'un développeur multi-support

[DIM] pour les intimes :)

iPhone & Requete HTTP

Yop all,

A la demande général de Fabrice Bernhard, voici une classe que j’ai faite pour me simplifier la vie quand je dois faire une requête HTTP. Vous me direz « pas besoin blabla », mais cette classe gère l’envoie d’images, de sons en plus de simple paramètres POST classique. Vous faites moins les malins hein :) Et si je vous dit qu’elle gère aussi si la requête doit être fait en mode synchrone ou asynchrone hein ? Ça t’en bouche un coin ?

Bah en fait elle est très simple, j’ai juste joué au légo avec les classes Apple, mais c’est un lego qui me sert pas mal.

Voici un exemple d’utilisation, ce code est tiré de l’exemple que j’ai fait pour vous, disponible en téléchargement :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
-(IBAction) send
{
// Crée la requete en mode asynchrone
HttpRequest *masuperRequete = [[HttpRequest alloc] initHttpRequest:formUrlAsk.text isSynchronous:false];
 
// Ajoute un parametre POST['cleParam'];
[masuperRequete addParam:@"cleParam" valeurs:formParam.text];
 
// Ajoute un fichier Image. Les parametres seront sous la forme file1, file2, file3 .. etc
[masuperRequete addImage:imageTmp.image];
 
// ajoute un son
// On doit recuperer les données du son sous la forme d'un NSDATA
// Ici le son est distant .. on le recupère donc avec the super classe en mode syncrhone
HttpRequest *sousReq = [[HttpRequest alloc] initHttpRequest:formUrlSound.text isSynchronous:true];
NSData * dataSound = [sousReq send];
 
// Recupere le nom du son
NSArray * mots = [formUrlSound.text componentsSeparatedByString:@"/"];
NSString *nameoffile = [mots lastObject];
 
[masuperRequete addSound:dataSound nameoffile:nameoffile nameinform:@"mamusique"];
 
// Envoie la superRequete
masuperRequete.delegate = self;
[masuperRequete send];
}
 
// y a un delegate pour recup le retour de la requete en mode asynchrone
-(void)downloadFinish:(NSMutableData *)data
{
NSLog(@"telechargement termine");
}

Maintenant voici le code source complet de l’exemple

Exemple de la classe HTTPRequest

Pour récupérer les données par la suite, rien de plus simple tout est dans $_POST et $_FILES.

maj : voilà le code source du fichier php que j’ai utilisé :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
 
<?php
 
$content_dir = 'upload/';
 
if(count($_POST) > 0 || count($_FILES) > 0)
{
 
 
 
 
	if(count($_POST) > 0)
	{
		echo "variable POST : \n";
		foreach($_POST as $cle=>$val)
			echo $cle . ' => ' . $val ."\n";
 
		if(isset($_POST['monson']))
		{
			$test = base64_decode($_POST['monson']);
 
			$file = fopen("upload/son.caf","w+");
 
			fwrite($file,$test);
 
			fclode($file);
		}
 
	}
 
	if(count($_FILES) > 0)
	{
		echo "variable FILES : \n";
		foreach($_FILES as $cle=>$image)
		{
 
			$tmp_file = $image['tmp_name'];
 
			if(!empty($tmp_file))
			{
 
 
				if( !is_uploaded_file($tmp_file) )
				{
					exit("Le fichier est introuvable");
				}
 
				// on vérifie maintenant l'extension
				$type_file = $image['type'];
 
				if( !strstr($type_file, 'jpg') &#038;&#038; !strstr($type_file, 'jpeg') &#038;&#038; !strstr($type_file, 'bmp') &#038;&#038; !strstr($type_file, 'png') &#038;&#038; !strstr($type_file, 'gif') )
				{
					//exit("Le fichier n'est pas une image");
				}
 
				// on copie le fichier dans le dossier de destination
				$name_file = $image['name'];
 
				echo $cle. " => " .$name_file ." reçu \n";
 
				if( !move_uploaded_file($tmp_file, $content_dir . $name_file) )
				{
					exit("Impossible de copier le fichier dans $content_dir");
				}
 
			}
	}
}
}
else
{
	 $rep = "upload/";
	$dir = opendir($rep);
 
	while ($f = readdir($dir)) {
  	 if(is_file($rep.$f)) {
    		echo "<li>Nom : ".$f;
		echo '<a href="upload/'.$f.'"> DL </a>';
      		echo "<li>Taille : ".filesize($rep.$f)." octets";
      		echo "<br /><br />";
   		}
	}
}
 
 ?>

Précisons quand même que ma classe est en alpha, certaines fonctionnalités ne sont pas implémentés. Je pense notamment à la gestion des erreurs, le qui donne le taux de progression de l’upload etc. Je vous laisse le faire :p

Enjoy !

Image representing Apple as depicted in CrunchBase

Image via CrunchBase

Tags : , , , ,

Gobelin news

Salut à tous,

Vous avez remarqué qu’il n’y avait pas de news depuis un bout de temps. Non pas que je glande, j’ai découvert plein de chose sur notamment grâce à l’excellent site icodeblog.com, mais parce que je suis sur un projet de fin d’année « Gobelin ».

En fait nous développons un nouveau type de réseau social décliné sur le web, le desktop, et sur Android. Les technologies sont donc « Symfony, ,  » dans l’ordre. Nous sommes une équipe de 3, et chacun développe une des déclinaisons, je m’occupe de la partie .

Ce projet doit être fini pour fin juin, donc en attendant il se peut que ce blog soit en stand-by. Actuellement je découvre encore Android, et je suis en train de préparer une série de billet pour vous en apprendre plus, je ne sais pas encore quand je les publierais.

voilà voilà vous savez tout.

Tags :

iPhone & Delegate

Bonjour,

Voici juste un petit « How to » pour comprendre et jour avec les sur cocoa.

Admettons vous avez une classe « ListingController » qui hérite d’une UIViewController qui gère une liste d’élément et que vous avez une classe « DetailController » qui hérite aussi d’une UIViewController mais qui donne le detail de l’élément cliqué.

Vous voulez que lorsqu’on click sur un bouton posé sur la vue Détail revenir à la liste principal, puis la recharger. Revenir à la vue principal est assez simple il suffit de crée une méthode « IBAction » dans le controller Detail, liée le bouton à cette méthode via Interface builder puis écrire  :

1
2
3
-(IBAction)goHome {
[self.view removeFromSuperview];
}

Et le tour est joué mais bon, la liste n’est pas rechargé pour autant et il n’y a pas eu la levé d’un évènement pour la classe « Liste ». C’est là que les delegate peuvent nous être utile.

Le principe est de définir des méthodes sur la classe de Detail puis les appeller/utiliser lors du click mais c’est la classe Liste qui les implémentera effectivement.

Voici la marche à suivre :

  1. Dans la classe Detail : Définir une ou plusieurs méthodes dite « delegate » qui devront être implémenter. On appelle ce genre de déclaration un « protocole ».
  2. Dans la classe Detail : Définir une propriété de classe de type « id » qui utilise le protocole. Cette propriété représente un lien vers la classe « parente » qui implémentera effectivement notre protocole.
  3. Dans la classe Detail : Appeller lors du click les méthodes « delegate » sur notre protocole.
  4. Dans la classe Liste : Implémenter les méthodes delegate dans notre classe « parente ».

Etape 1 :

Dans le fichier « DetailController.h », juste après les #import et avant @interface DetailController : UIViewController { } :

1
2
3
4
@protocol DetailControllerDelegate
@required
-(void)RetourListe;
@end

Etape 2 :

Toujours dans le fichier « DetailController.h », après « @interface DetailController : UIViewController { » nous définissons une propriété que nous appellerons « delegate » :

1
id delegate;

puis après l’accolade définissant l’interface :

1
2
3
@property (nonatomic,assign) id < DetailControllerDelegate > delegate;
 
@end

Notre propriété « delegate » est de type « id » mais « implémente » le protocole précédement utilisé.

Etape 3 :

Nous avons définit une propriété dans le fichier « .h », il faut maintenant s’en servir dans notre fichier « DetailController.m » :

1
2
3
4
5
6
7
8
9
#import "DetailController.h"
@implementation DetailController
@synthesize delegate; //synthetisé une propriété signifie que le sdk créera les getters/setters pour nous
-(IBAction)goHome
{
	NSLog(@"Go home Liste");
	[self.delegate RetourListe]; // dispatch l'évènement à la classe parente.
	[self.view removeFromSuperview]; // supprime la vue courante
}

C’est fini pour notre classe Detail  ! Vous remarquerez qu’à aucun moment il n’y a de lien vers la classe « Liste ». Le couplage est donc très faible entre ces classes. Ce principe de couplage faible est assez puissant si on y réfléchi à deux fois vu que nos classes sont, de fait, totalement indépendantes vis à vis de leur « parent » (ou du contexte d’execution pour être plus bien parlant). Bon bref ça c’était le coup de pub raté pour la puissance du modèle objet.

Etape 4

Implémentons les méthodes ! Dans le fichier « ListingController.h » nous déclarons notre interface comme ceci :

1
2
3
@interface ListingController : UIViewController < DetailControllerDelegate > {
// some code
}

Et dans notre fichier « ListingController.m » il suffit d’implémenter la méthode « RetourListe ». Surtout, ne pas la déclarer dans l’interface (le fichier .h) sinon le programme se mélangera les pinceaux ! (Oui, vous utilisez deux méthodes avec le même nom ayant des portés différentes ..)

1
2
3
4
5
-(void)RetourListe
{
    NSLog("coucou");
    // some code
}

Edit : Oups, erreur. On crée la méthode mais à un aucun moment on dit que notre classe Listing va implémenter les méthodes de detail hormis dans la déclaration (fichier.h). Il faut donc qu’à notre objet « DetailController », juste après qu’il soit instanciée, au moment du click par exemple faire un :

1
2
DetailController*myView = [[DetailControlleralloc] initWithNibName:@"DetailView" bundle:[NSBundle mainBundle]];
myView.delegate = self;

On définit que c’est la classe Listing (self) va implémenter les méthodes delegate de détail.

Et voilà, normallement ça doit marcher.

Ce « How to » a été écrit à partir d’un projet personnel, je ne pourrais donc pas donnée des sources qui utilisent ce concept. A l’heure où j’écrit ce billet je n’ai pas de Mac à ma disposition.

Tags : , , , ,

Android vs Iphone

Aujourd’hui, je dois créer une application pour un projet de cours. VDM.

En effet, Android peut faire peur :

  • tout est en java
  • un simulateur horriblement moche
  • une documentation light light
  • pas d’outil « friendly » et fun pour faire des interfaces. Aucune vidéo « qui pete sa race »
  • propulsé par le dieu de l’information Google à grand coup de millions de $

Il est vrai que c’est rebutant à première vue et l’on peut penser que c’est un peu masochiste de developper sur Android. Mais de mon expérience « Iphonesque » je dirais que ça se vaut, voyez par vous même :

me fait peur :

  • tout est en Objective-C/C/C++ OMG
  • un simulateur esthétiquement jolie mais rien sous le capot
  • une documentation étouffante, y a des guides, des vidéos, des définitions de classes .. y en a trop à mon gout.
  • pas de génération de code comme avec eclipses, l’auto-completion mollassonne et pas franchement efficaces
  • propulsé par le dieu de la manipulation Apple et son cercle « VIP/expert » de m***

Chacun ses gouts après, si l’on est plus « ça pete c’est beau ca suffit » je dirais que l’iphone est plus adaptés qu’android. A contrario on veut faire du code sympa Android c’est plus abordable.

Perso, le code ça me botte plus qu’une jolie interface. Je préfère donc Android pour la philosophie qu’il a derrière. C’est en grande partie open-source, on peut avoir accès aux codes des autres, aux milliers de librairie JAVA qui existent plus simplement.

Ces prochains jours j’essaierais de faire des billets sur des exemples de de programmes sur android. Au programme : hello world, la gestions des boutons et menus, passez d’un écran à l’autre, faire des requetes Web, parser de l’XML, jouer avec les listes et la personnalisation des cellules.

Bonne journée et à bientôt pour une session Android :)

Tags : , ,

iPhone & AVAudioPlayer

Aujourd’hui, j’ai bataillé pour lancer un mp3 au lancement d’une application. Tellement bataillé que je vais mettre ici le code qui va bien pour que je puisse le retrouver rapidement :) Donc peu d’explication, ceci n’est pas un tutorial.

  1. Inclure le framework AudioToolBox & AVFoundation
  2. Faire les imports suivants : #import &lt;AVFoundation/AVFoundation.h&gt; #import &lt;AudioToolbox/AudioToolbox.h&gt;
  3. Inclure le fichier à jouer dans le projet dans le dossier « ressources »
  4. Déclarer une propriété de classe AVAudioPlayer * audioPlayer;

Et voici le code qui va bien :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
	// Initialise un contexte Audio
	AudioSessionInitialize (
							NULL,                          // 'NULL' to use the default (main) run loop
							NULL,                          // 'NULL' to use the default run loop mode
							NULL,  // a reference to your interruption callback
							self                       // data to pass to your interruption listener callback
							);
 
	// Definit quel type de son on va joué
	UInt32 sessionCategory = kAudioSessionCategory_MediaPlayback;
	AudioSessionSetProperty (
							 kAudioSessionProperty_AudioCategory,
							 sizeof (sessionCategory),
							 &#038;sessionCategory
							 );
 
	// Définit quel fichier on utilise pour le son
	NSData *soundFileData;
	soundFileData = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"music.mp3" ofType:NULL]]];
 
	// Initialise notre lecteur avec les données du fichiers son à jouer
	audioPlayer = [[AVAudioPlayer alloc] initWithData:soundFileData error:NULL];
 
	// Precharge le son
	if(!([audioPlayer prepareToPlay])){
		NSLog(@"myAudioPlayer:prepareToPlay returned FALSE");
	}
 
	// Définit le delegate sur la classe courante
	audioPlayer.delegate = self;
 
	// Définit le volume
	[audioPlayer setVolume:1.0];
 
	// Joue le son !
	[audioPlayer play];

Voilà, la seul différence avec les codes que l’on peut trouver un peu partout est l’ajout du Session Audio. Si je l’initialise pas le son ne fonctionne que sur le simulateur et pas sur le mobile. Je ne sais pas trop pourquoi.

Merci à fpillet du channel irc #iphonedev sur freenode pour son aide.

Tags : , , ,

Iphone & UIButton & Multilangue

Vaste programme pour ce billet :) En fait nous allons modifier un bouton en fonction de la langue du téléphone.
Et nous allons créer un état « survolé » à notre bouton par programmation. InterfaceBuilder n’étant pas très clair sur cette manipulation.

Tout d’abord il nous faut un UIButton déclaré en tant que IBOutlet UIButton dans notre classe. Puis nous relions ce bouton via InterfaceBuilder avec un bouton sur notre vue. Dans les propriétés de celui ci vous pouvez dire que c’est un « custom button ». Faites le. Et nous revenons à XCode :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
-(void)viewDidLoad
{
	// Gestion du multilanguage
	NSUserDefaults* defs = [NSUserDefaults standardUserDefaults];
	NSArray* languages = [defs objectForKey:@"AppleLanguages"];
 
	// preferredLang vaut "fr", "en", "de", etc 
	preferredLang = [languages objectAtIndex:0];
 
	// Méthode avec deux jeux de langues : Française ou Anglaise dans les autres langues 
	if([preferredLang isEqualToString:@"fr"])
	{
		UIImage * higlight = [UIImage imageNamed:@"fr_btn_clik.png"];
		[btnTest setBackgroundImage:higlight forState:UIControlStateHighlighted];
		[higlight release];
 
		UIImage * normal = [UIImage imageNamed:@"fr_btn.png"];
		[btnTest setBackgroundImage:normal forState:UIControlStateNormal];
		[normal release];
	}
	else 
	{
		UIImage * higlight = [UIImage imageNamed:@"en_btn_clik.png"];
		[btnTest setBackgroundImage:higlight forState:UIControlStateHighlighted];
		[higlight release];
 
		UIImage * normal = [UIImage imageNamed:@"en_btn.png"];
		[btnTest setBackgroundImage:normal forState:UIControlStateNormal];
		[normal release];
	}
 
	// Mais nous pouvons aussi concaténer le code langue avec le chemin de l'image.
	// dans ce cas là il nous faut autant d'image que de langue possibles.
	NSString * pathImage = @"_btn.png";
	NSString * pathImageClik = @"_btn_clik.png";
	UIImage * higlight = [UIImage imageNamed:[pathImage stringByAppendingString:preferredLang ] ];
	[btnTest setBackgroundImage:higlight forState:UIControlStateHighlighted];
	[higlight release];
 
	UIImage * normal = [UIImage imageNamed:[pathImageClik stringByAppendingString:preferredLang ]];
	[btnTest setBackgroundImage:normal forState:UIControlStateNormal];
	[normal release];
 
}

Simple non ?

Après c’est sur cette méthode est un peu lourde. Il faut autant d’images que de langage et on n’utilise pas les techniques Apple pour le multilangue (avec leurs histoire de Bundle par langue, un dictionnaire de traductions etc).

Cependant, si comme vous avez affaire à des boutons très personnalisés (typo exotique, disposition du texte particuliere), il est peut être plus rapide de tout passer en image comme ici.

Chacun fait comme il le sent après ^^

Tags : , , , ,

iPhone & UIView

Connaissez vous l’attribut « tag » d’une UIView ?

Cette propriété sert à différencier les vues les unes des autres. Il s’agit d’un entier qui, par défaut, est géré par l’. Mais on peut la fixer à la main et cela permet quelque liberté. En voici une utilisation :

Admettons que vous avez une vue centrale qui sert de menu en « footer » et que vous n’utilisez pas les composants de navigations par défaut du téléphone.

Lors d’un clic sur un bouton du menu « footer », vous allez ajouter une nouvelle vue par dessus votre écran principal en laissant le footer de la vue principal visible. Seulement voilà, à chaque clic sur un bouton du menu, vous allez superposer une vue à chaque fois. Question performance c’est pas tip top.

Alors une première approche est déjà de faire un bouton retour dans la nouvelle vue comme ceci :

1
2
3
4
5
- (IBAction)back {
 
NSLog(@"back");
[self.view removeFromSuperview];
}

Mais bon rien n’empêche l’utilisateur de clicker sur les éléments du footer au lieu de faire retour. Notez que même si cette nouvelle vue est ajouté par dessus la principale et la masque complètement, les autres boutons de la vue principale sont encore actifs. Sauf si on les désactive lors du click sur le bouton et qu’on les réactives lors de l’action « back » précédemment crée, via une méthode .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 
// Dans la vue qui se superpose
- (IBAction)back {
	NSLog(@"back");
	[self.delegate RetourMenu];
	[self.view removeFromSuperview];
}
 
// Dans la vue principale
-(void)RetourMenu
{
	NSLog(@"Réactivation des boutons "cachés" par la nouvelle superposés");
	btnVue1.enabled = YES;
	btnVue2.enabled = YES;
	btnVue3.enabled = YES;
 
}

Il faut maintenant gérer les vues afin d’éviter les superposements inutiles. Ceci se fait via la propriété tag. Personnellement j’ai définit le tag de ma vue principal à 100 via Interface Builder. Mais je pense qu’un self.tag = 100 dans le viewDidLoad marche aussi.

Puis voici comment je change de vue :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- (IBAction)ClickMenu{
	// Vide l'écran de toutes vues, sauf celle de l'écran principale
	NSArray *subviews = [[self.view superview] subviews];
	UIView *view;
	for (view in subviews)
	{
		// Le tag 100 correspond à la vue "Menu Principale"
		if(view.tag != 100)
		{
			[view removeFromSuperview];
		}
	}
	// Définit la vue courante et le gestionnaire d'évènement de MaVueSuivante
	MaVueSuivante*myView = [[MaVueSuivantealloc] initWithNibName:@"MaVueSuivanteNib" bundle:[NSBundle mainBundle]];
	myView.delegate = self;
 
	// Recupère le singleton de l'application courante
	MonProjetAppDelegate *app = (Lacoste_0_MonProjetAppDelegate *)[[UIApplication sharedApplication] delegate];
	[app.window addSubview:myView.view];
 
	// Désactivation des boutons "cachés" par la nouvelle vue
	btnVue1.enabled = NO;
	btnVue2.enabled = NO;
	btnVue3.enabled = NO;
 
}

Simple non ?

Enfin pour rendre une vue par dessus une autre tout en gardant la vue principal visible j’utilise la propriété « bounds » de la vue à afficher comme ceci :

1
2
3
4
- (void)viewDidLoad
{
	view.bounds = CGRectMake(0, 0, 320, 450); // Une hauteur de -30 pixels par rapport à la normal. 
 }
Tags : , , ,

iPhone & TableView

Coupé depuis 1 mois d’internet à domicile, je n’ai pas trop le temps de blogger au boulot… Ne faisans pas trop de veille technologique et ne pouvant parler des projets d’entreprises je n’ai pas grand chose à raconter sous forme de long .

J’entame donc une nouvelle façon de blogger mes découvertes : à travers de brêves. Aujourd’hui, pour renouer avec le lectorat «  », il sera question de TableView !

TableView

Avez vous déjà essayé de rendre une ligne sur deux d’une couleur différente (le background, les textes des labels et leurs états « survolés » ? Hey bien c’est la merde ! Voici la solution que j’ai pu trouver ici et là.

  • Avoir une classe qui gère une cellule.
  • Avoir une méthode de colorisation efficace pour la cellule
  • Détecter si la cellule courante est pair ou impair.

Lire la Suite

Tags : , , , ,

Découverte de la semaine

Bonjour,

Je suis en ce moment en pleine configuration d’un serveur Kimsufi (OVH), du coup je n’ai pas le temps de découvrir de nouvelles choses cotés prog. Mais bon, un bon développeur web se doit aussi de connaitre son hébergement et qu’est ce qu’il peut faire avec etc.

Si comme vous voulez un hébergement avec un petit serveur Subversion derrière, surtout ne prenez pas la configuration « Hosting web » de Kimsufi. Leur realese RC2 de gentoo est vraiment configuré bizarrement et rajouter des fonctionnalités n’est pas évident du tout !

Mais heuresement qu’une fois que vous avez votre serveur, vous pouvez changer la configuration en 4 clics sur le manager OVH pour changer de distrib et ça se fait en 10mn ! Pour ma part, je suis parti dans une distrib nue Ubuntu serveur 8.10 et j’en suis bien content. Ubuntu est très bien documenté et c’est assez simple de configurer son serveur au final. (N’est ce pas Ben ? ^^)

Ubuntu logo

Image via Wikipedia

Voilà, c’était une news très brève (pour une fois ^^)


Cet article a été rédigé via l’aide du plugin wordpress de Zemanta, découvert sur ReadWriteFrance. Il permet au rédacteur d’automatiser certaines taches comme la création de lien, la recherche d’image en rapport avec ceux que l’on écrit, le taggage de l’article etc. C’est assez « confortable » comme plugin. Le seul reproche : tout ce qu’il trouve vient du web anglais. Dommage.

 

Reblog this post [with Zemanta]
Aucun tag pour cet article.

SQlite & Air

Un peu d’SQL ça fait pas de mal non ?

Je fais que peu de back admin en ce moment … j’écrit quasi pas de requêtes .. vive les ORM (object-relational mapping) .. et j’occupe mon temps libre à Flex/Flash et mon application de suivi de stats sportives.

Je suis en train d’écrire une nouvelle version de mon application, avec un code plus propre, et surtout multi utilisateur afin que chacun inscrive ses statistiques sportives dans la même application. Je pourrais m’en sortir en utilisant à mort les HTTPService, faire un webservice et site dédié à la cause.. mais bon je n’ai pas vocation (pour l’instant) de créer un site pour se montrer le nombril … et imaginer le nombre de requêtes à traiter …

En sachant que embarque nativement un moteur SQLite, pourquoi ne pas garder sur le poste du client toutes ses stats ? Quitte à faire par la suite un bouton « synchroniser sur le web » :) Ca peut le faire non ?

Lire la Suite

Tags : , , ,