IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C++ Discussion :

gestionnaire de ressources


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre confirmé
    Profil pro
    Inscrit en
    Novembre 2006
    Messages
    136
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2006
    Messages : 136
    Par défaut gestionnaire de ressources
    Bonjour,

    Je voudrais un simple avis sur mon code (en C++). Je n'ai pas encore écrit les lignes de code mais seulement l'enveloppe globale. L'idée est de se servir du type retour pour savoir le type de classe attendu, et ainsi créer ou vérifier que les types correspondent, mais est-ce que ça n'est pas trop dangereux pour le programme, bancal niveau programmation ou trop lourd à l'exécution?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    class gestionnaire_ressources : Singleton<gestionnaire_ressources>  //classe singleton, un design pattern
    {
        friend class Singleton<gestionnaire_ressources>;   //nécessaire pour singleton
     
        public:
        template<typename T> T* getRessource(char* chaine);     //+1 à l'utilisation
        void delRessource(char* chaine);                        //-1 à l'utilisation
     
        protected:
        gestionnaire_ressources();
        ~gestionnaire_ressources();
     
        private:
        std::map<std::string, &ressource> conteneur;
        std::map<std::string, &ressource>::iterator it;
    };
     
     
    class ressource
    {
        friend class gestionnaire_ressources;
        public:
        virtual void charger(char* chaine) = 0;    //classe virtuelle pure + obligation de redéfinir
        int getUtilisation();
     
        protected:
        ressource();
        ~ressource();
        int utilisation;
        void del();
        void add();
     
        private:
        ressource* pointeur;
    };
    L'idée de base de ce système est que toute ressource est sur le disque dur, donc chargée par le programme avec son chemin. De plus je m'arrangerai pour toujours avoir des chemins relatifs (pour éviter de doubler une ressource en mémoire).

    J'ai essayé d'utiliser celui proposé par Laurent Gomila mais je n'y comprends rien pour l'instant (et il me semble trop compliqué pour les besoins de mon programme) néanmoins je m'en suis inspiré .

    Merci pour votre lecture!

  2. #2
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Salut,

    De manière générale, il ne faut jamais placer une référence sur un objet dans un conteneur... Pour la simple et bonne raison qu'une référence n'est jamais qu'un alias d'un objet existant, et que celui-ci est soumis aux règles habituelles de destruction lors de la sortie de la portée dans laquelle il est déclaré.

    D'ailleurs, c'est bien simple, il me semble qu'il est de toutes manières impossible d'instancier un conteneur standard de manière à ce qu'il utilise des références...

    C'est la raison pour laquelle il est préférable, pour un gestionnaire de ressources, de travailler sur des ressources pour lesquelles la mémoire est allouée dynamiquement.

    Ce qui, en soit, nous arrange finalement bien, étant donné qu'un pointeur est malgré tout idéal pour gérer le polymorphisme

    Ce qu'il faut donc envisager de faire est relativement simple:

    Considère ta classe Ressource comme une interface (une classe abstraite) commune à toutes les ressources réelles que tu va gérer.
    Fais dériver toutes les ressources réelles ta classe Ressource
    Maintient une map de pointeurs vers la classe Ressource, le tout en n'oubliant pas de déléguer correctement les responsabilités

    Le code ressemblerait donc à quelque chose proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    class Ressource;
    class gestionnaire_ressource:  public Singleton<gestionnaire_ressource>
    {
        public:
            void enregistreRessource(Ressource* toadd)
            {
                assert(toadd!=NULL);
                conteneur.insert(std::make_pair(toadd->nom(),toadd));
                it=conteneur.find(name);
            } 
            void suppressRessource(const std::string& name)
            {
                it=conteneur.find(name);
                if(it==conteneur.end())
                {
                     /* il faudrait gérer le fait que l'élément ne soit pas trouvé
                      * ici
                      */
                }
                delete (*it).second;
                conteneur.erase(it);
            }
            Ressource* trouveRessource(const std::string& nom)
            {
     
                it=conteneur.find(name);
                if(it==conteneur.end())
                {
                     /* il faudrait gérer le fait que l'élément ne soit pas trouvé
                      * ici
                      */
                }
                return (*it).second;
            }
            /* pourquoi pas */
            void chargeToutesRessources()
            {
                for(it=conteneur.begin();it!=conteneur.end();++it)
                    (*it).second->chargerRessource();
            }
     
            /* utile pour nettoyer tout */
            void suppressToutesRessources()
            {
     
                for(it=conteneur.begin();it!=conteneur.end();++it)
                    delete (*it).second;
                conteneur.clear();
            }
        private:
            std::map<std::string, Ressource*> conteneur;
            std::map<std::string, Ressource*>::iterator it;
    };
    class Ressource
    {
        public:
            /*pour une ressource générale, tout ce qu'on connait, c'est son nom */
            Ressource(const std::string& mnom):mnom(mnom){}
            virtual ~Ressource(){}
            virtual void chargerRessource() = 0;
            virtual void utiliserRessource(/*paramètres éventuels */) = 0;
            const std::string& nom() const{return mnom;}
        private:
            std::string mnom;
    };
    et, pour les ressources réelles, tu en arriverait à un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class RessourceTXT : public Ressource
    {
        public:
            RessourceTXT(const std::string& mnom):Ressource(mnom){}
            virtual ~RessourceTXT();
            virtual void chargerRessource();
            virtual void utiliserRessource(/* paramètres éventuels */);
        private:
            /*les membres qui vont bien avec cette ressource */
    };
    Après, si le besoin s'en fait réellement sentir, rien ne t'empêche de prévoir un moyen de connaitre le type de ressource auquel tu as affaire sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    enum eType
    {
        tTXT, //ressources "Texte"
        tXML, //ressource "XML"
        tMP3, //ressource "MP3"
        tGIF, //ressource "gif"
        tJPG, //ressource "jpeg"
        /*...*/
    };
    /*modifions l'interface en conséquence */
    class Ressource
    {
        public:
            /*pour une ressource générale, tout ce qu'on connait, c'est son nom */
            Ressource(const std::string& mnom, etype tt):mnom(mnom),mtype(tt){}
            virtual ~Ressource(){}
            virtual void chargerRessource() = 0;
            virtual void utiliserRessource(/*paramètres éventuels */) = 0;
            const std::string& nom() const{return mnom;}
            etype ressourceType() const{return mtype;}
        private:
            std::string mnom;
            etype mtype;
    };
    /* et le constructeur des ressources concrète sous une forme proche de */
    RessourceTXT::RessourceTXT(const std::string& mnom):
                  Ressource(mnom,tTXT)
    {
    }
    RessourceXML::RessourceXML(const std::string& mnom):
                  Ressource(mnom,tXML)
    {
    }
    /*...*/
    void uneFonction(RessourceTXT* r)
    {
       /*fonction propre aux ressources texte par exemple*/
       std::cout<<"cette fonction s'applique a un fichier texte"<<std::endl;
    }
    void uneFonction(RessourceXML* r)
    {
       /*fonction propre aux ressources texte par exemple*/
       std::cout<<"cette fonction s'applique a un fichier XML"<<std::endl;
    }
     
    /* et une fonction pour mettre tout cela en oeuvre: */
    void lafonctionFinale(const std::string& lenom)
    {
        Ressource* res=GestionnaireRessource::getInstance()->trouveRessource(lenom);
        switch(res->ressourceType())
        {
            case tTXT:
                uneFonction(static_cast<RessourceTXT*>(res));
                break;
            case tXML:
                uneFonction(static_cast<RessourceXML*>(res));
                break;
            /*...*/
            default:
                break;
        };
    }
    Au final, cela pourrait être utilisé sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
     
    int main()
    {
        /* on récupère une instance valide du gestionnaire */
        GestionnaireRessource * gest=GestionnaireRessource::getInstance();
        /* on enregistre de nouvelles ressource, sans les charger */
        gest->enregistreRessource(new RessourceTXT("unnom.txt") );
        gest->enregistreRessource(new RessourceXML("lenom.xml") );
        /* on charge toutes les ressources */
        gest->chargeToutesRessources();
        /* on appelle la fonction finale pour le fichier texte */
        lafonctionFinale("unnom.txt");
        /* puis pour le fichier XML */
        lafonctionFinale("lenom.xml");
        /* ou, étant donné que l'on dispose du polymorphisme */
        Ressource* trouve=gest->trouveRessource("unnom.txt");
        trouve->utiliserRessource();
        /* on nettoie tout, car on est bien élevé */
        gest->suppressToutesRessources();    
        return 0;
    }
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  3. #3
    Expert confirmé
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Décembre 2003
    Messages
    3 549
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Décembre 2003
    Messages : 3 549
    Par défaut
    Boost.Flyweight te permet une gestion transparente de ce genre de trucs.

  4. #4
    Membre confirmé
    Profil pro
    Inscrit en
    Novembre 2006
    Messages
    136
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2006
    Messages : 136
    Par défaut
    Merci pour ce code mais il ne correspond pas exactement à ce que je pensais. En tout cas merci pour ton aide koala01 (j'ai changé pas mal de chose grâce à toi).

    Le fichier .h:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    class ressource;
    class gestionnaire_ressources : Singleton<gestionnaire_ressources>  //classe singleton, un design pattern
    {
        friend class Singleton<gestionnaire_ressources>;   //nécessaire pour singleton
     
        public:
        template<typename T> T* getRessource(char* chaine);     //+1 à l'utilisation
        void delRessource(char* chaine);                        //-1 à l'utilisation
     
        template<typename T> T* recuperer(char* chaine);        //sans +1 à l'utilisation, on sait jamais
     
        void vider();                                           //permet en fin de programme de vérifier qu'il n'y-a pas de fuites
     
        protected:
        gestionnaire_ressources(){}
        ~gestionnaire_ressources(){}
     
        private:
        std::map<std::string, ressource*> conteneur;
        std::map<std::string, ressource*>::iterator it;
    };
     
     
    class ressource
    {
        friend class gestionnaire_ressources;
        public:
        virtual void charger(char* chaine) = 0;    //classe virtuelle pure + obligation de redéfinir
        int getUtilisation(){return utilisation}
     
        protected:
        ressource(){}      //seul le gestionnaire peut créer une ressource
        ~ressource(){}
        int utilisation;  //le gestionnaire peut y accéder directement
    };
    Le fichier .cpp :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    template<typename T> T* gestionnaire_ressources::getRessource(char* chaine)
    {
        T* pointeur_T = new T;   //importance d'avoir un constructeur qui ne sert qu'au minimum
     
        //tester le type (manque si T est ressource, mais bon on suppose que quand même...
        ressource* ressource_p = dynamic_cast<ressource*>(pointeur_T);
        if(ressource_p == NULL)
        {
            throw err("mauvais type donné (1) : " + typeid(T).name());
        }
        //ok
     
        std::string chaine1 = chaine;
     
        it = conteneur.find(chaine1);
     
        if (it == conteneur.end())
        {
            pointeur_T->charger(chaine); //on réustilise
     
            pointeur_T->utilisation = 1;
     
            conteneur.insert(chaine1,ressource_p);  //ressource_p toujours valable
     
            return pointeur_T;
        }
     
        else
        {
            //retest du type
            delete pointeur_T;
     
            pointeur_T = dynamic_cast<T*>(it->second);
     
            if (pointeur_T == NULL)
            {
                throw err("mauvais type donné (2) : " + typeid(T).name());
            }
     
            it->utilisation += 1;
     
            return pointeur_T;
        }
     
        //ne devrait JAMAIS être là
    }
     
    void gestionnaire_ressources::delRessource(char* chaine)
    {
        std::string chaine1 = chaine;
        int utilisation = 0;
     
        it = conteneur.find(chaine1);
     
        if (it == conteneur.end())
        {
            throw err("Suppression de ressource inexistante : " + chaine1);
        }
     
        utilisation = it->second->utilisation;
        utilisation -= 1;
     
        if(utilisation == 0)
        {
            delete it->second;
     
            conteneur.erase(it);
        }
    }
     
    template<typename T> T* gestionnaire_ressources::recuperer(char* chaine)
    {
        T* pointeur_T = new T;   //importance d'avoir un constructeur qui ne sert qu'au minimum
     
        std::string chaine1 = chaine;
     
        //tester le type (manque si T est ressource, mais bon on suppose que quand même...
        ressource* ressource_p = dynamic_cast<ressource*>(pointeur_T);
        if(ressource_p == NULL)
        {
            throw err("mauvais type donné (1) : " + typeid(T).name() + " pour " + chaine1);
        }
        //ok
     
        delete pointeur_T;
     
     
        it = conteneur.find(chaine1);
     
        if (it == conteneur.end())
        {
            throw err("récupération impossible pour " + chaine1);
        }
     
        else
        {
            //retest du type
     
     
            pointeur_T = dynamic_cast<T*>(it->second);
     
            if (pointeur_T == NULL)
            {
                throw err("mauvais type donné (2) : " + typeid(T).name());
            }
     
            it->utilisation += 1;
     
            return pointeur_T;
        }
     
        //ne devrait JAMAIS être là
    }
     
    void gestionnaire_ressources::vider()
    {
        if(!conteneur.empty)
        {
            std::string chaine1,chaine2;
            int utilisation = 0;
            Logger* log = Logger::getInstance();
     
            for(it = conteneur.begin; it != conteneur.end; ++it)
            {
                chaine1 = it->first;
                utilisation = it->second->utilisation;
     
                delete it->second;
     
                conteneur.erase(it);
     
                chaine2 = "chemin : " + chaine1 + "   utilisation : " + utilisation + "\n";
     
                log->logOut(chaine2);
            }
     
            log->logOut_EndLine();
     
            throw err("Probleme : fuite de mémoire, voir le log");
        }
    }
    J'utilise beaucoup les nouveaux casts et la RTTI. Est-ce que ce n'est pas un peu trop lourd du coup? Et sinon est-ce fiable comme code?

  5. #5
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Il faut savoir que dynamic_cast et typeid ont un cout non négligeables à l'exécution...

    Alors qu'une énumération, par exemple, ne va couter, en définitive, que la mémoire nécessaire au maintien d'un entier suplémentaire dans la structure, et rend sans aucun problème exactement les mêmes services...

    Tu pourrait même envisager une exception du genre de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    class MyConversionError
    {
        public:
            MyConversionError(const std::string& name, eType t)
            {
                std::stringstream ss;
                ss<<name <<"ne peut pas être converti en ressource de type ";
                switch(t)
                {
                     case tTXT: ss<<"texte";
                                     break;
                     case tXML: ss<<"XML";
                                     break;
                     case tMP3: ss<<"MP3";
                                     break;
                     /*...*/
                     default :
                              "inconnu";
                              break;
                };
                mwhat=ss.str();
            }
            ~ConvertionError() throw(){}
            const char* what() trhow(){return mwhat.c_str();}
        private:
            std::string mwhat;
    };
    et que le fait de tester "simplement" si le membre qui représente le type de l'élément correspond à une valeur correcte pour ce que l'on souhaite, ira le plus souvent bien plus vite que de passer par dynamic_cast ou par le rtti...

    En outre, je n'ai rien contre l'utilisation des templates, loin s'en faut, mais, je te conseille de méditer trente secondes sur la dernière partie de ma signature.

    En un mot, la règle d'or est toujours KISS (Keep It Simple, Stupid).

    Plus tu va compliquer les choses qui n'ont aucun besoin de l'être, plus tu cours le risque de rajouter un bug embêtant... et d'autant plus qu'il aurait sans doute été évité si tu avais gardé les choses simples.

    Enfin, hormis si tu prévois de travailler sur une bibliothèque dynamique, je te conseillerais volontiers de préférer les arguments sous la forme de référence constante sur une std::string plutôt que sous la forme de char*... ou, à tout le moins, de veiller à ce que ce soit au minimum des const char*
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  6. #6
    Membre confirmé
    Profil pro
    Inscrit en
    Novembre 2006
    Messages
    136
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2006
    Messages : 136
    Par défaut
    Merci pour ce conseil, je pense que c'est mieux. Par contre, est-ce qu'il n'y-a pas des risques d'erreurs du coup?

    Pour les const char, je les ai déjà changé il y-a peu. C'est plus simple. J'en ai besoin sous cette forme (mais je les stocke en string) pour ouvrir les fichiers mais en toute rigueur je devrais faire une surcharge de fonction.

  7. #7
    Membre Expert

    Profil pro
    Inscrit en
    Juin 2006
    Messages
    1 294
    Détails du profil
    Informations personnelles :
    Localisation : Royaume-Uni

    Informations forums :
    Inscription : Juin 2006
    Messages : 1 294
    Par défaut
    Salut,

    Citation Envoyé par Plomeg Voir le message
    Et sinon est-ce fiable comme code?
    Quelques trucs repérés qui ne sont pas terribles, en vrac :
    . la classe ressource est manifestement destinée à être dérivée, son destructeur doit être virtuel
    . faire un new juste avant un throw ça s'appelle une fuite mémoire (les pointeurs nus c'est souvent pas génial, utiliser std::auto_ptr par exemple)
    . il manque plein de const (paramètres et variables locales)
    . laisser à l'utilisateur le soin de se souvenir qu'il faut qu'il appelle delRessource et/ou vider c'est assez malhabile, il faudrait trouver mieux (en regardant ce qui peut se faire avec le RAII)
    . it n'a rien à faire en donnée membre de la classe, il faut utiliser des variables locales
    . chaine1 ne sert à rien on peut passer directement chaine à find dans gestionnaire_ressources::recuperer
    . il ne faut pas déclarer les variables locales avant d'en avoir besoin, par exemple chaine1 et chaine2 doivent être à l'intérieur de la boucle dans gestionnaire_ressources::vider()

    Après c'est plus philosophique et ça n'engage à la limite que moi :
    . les singletons c'est franchement pas terrible (à part cas restreints d'optimisation)
    . le mélange de français et d'anglais c'est assez... inhabituel...

    MAT.

Discussions similaires

  1. Gestionnaire de ressources
    Par Kromagg dans le forum Moteurs 3D
    Réponses: 4
    Dernier message: 05/06/2012, 23h45
  2. Gestionnaire de ressources sous Delphi 2010
    Par jackfirst72 dans le forum EDI
    Réponses: 0
    Dernier message: 15/04/2010, 08h39
  3. Mise à jour d'un Gestionnaire de Ressource
    Par -eXa- dans le forum C++
    Réponses: 7
    Dernier message: 06/08/2008, 08h28
  4. Gestionnaire de ressources
    Par lapos dans le forum Développement 2D, 3D et Jeux
    Réponses: 15
    Dernier message: 22/07/2007, 00h02
  5. [TUTO moteur 3D] gestionnaire de ressources
    Par new.proger dans le forum Développement 2D, 3D et Jeux
    Réponses: 6
    Dernier message: 29/06/2006, 20h55

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo