2 % Copyright 2008 Grégory Burri
4 % This file is part of Euphorik.
6 % Euphorik is free software: you can redistribute it and/or modify
7 % it under the terms of the GNU General Public License as published by
8 % the Free Software Foundation, either version 3 of the License, or
9 % (at your option) any later version.
11 % Euphorik is distributed in the hope that it will be useful,
12 % but WITHOUT ANY WARRANTY; without even the implied warranty of
13 % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 % GNU General Public License for more details.
16 % You should have received a copy of the GNU General Public License
17 % along with Euphorik. If not, see <http://www.gnu.org/licenses/>.
19 % Some functions to manage the database like creating, updating, backuping etc.
23 -module(euphorik_bd_admin
).
24 -author("Greg Burri <greg.burri@gmail.com>").
43 -import(qlc
, [e
/2, q
/1, cursor
/2]).
45 -include("../include/euphorik_bd.hrl").
46 -include("../include/euphorik_defines.hrl").
47 -include_lib("stdlib/include/qlc.hrl").
49 % Return the current version of the DB.
51 euphorik_bd:resultat_transaction(mnesia:transaction(
53 mnesia:read({proprietes
, version
})
57 % Create a the database. Warning: it will delete all existent data.
59 % $erl -sname yaws -mnesia dir '"/projects/euphorik/DB"'
61 % >euphorik_bd:create().
64 mnesia:delete_schema([node()]),
65 mnesia:create_schema([node()]), % nécessaire pour les tables sur disc
71 mnesia:create_table(counter
, [
72 {attributes
, record_info(fields
, counter
)},
73 {disc_copies
, [node()]}
75 mnesia:create_table(proprietes
, [
76 {attributes
, record_info(fields
, proprietes
)},
77 {disc_copies
, [node()]}
79 mnesia:create_table(texte
, [
80 {attributes
, record_info(fields
, texte
)},
81 {disc_copies
, [node()]}
83 mnesia:create_table(minichat
, [
85 {attributes
, record_info(fields
, minichat
)},
86 {disc_copies
, [node()]}
88 mnesia:create_table(reponse_minichat
, [
90 {attributes
, record_info(fields
, reponse_minichat
)},
91 {disc_copies
, [node()]}
93 mnesia:create_table(user
, [
94 {attributes
, record_info(fields
, user
)},
95 {disc_copies
, [node()]}
97 mnesia:create_table(ip_table
, [
98 {attributes
, record_info(fields
, ip_table
)},
99 {disc_copies
, [node()]}
101 % This table is not used anymore.
102 mnesia:create_table(troll
, [
103 {attributes
, record_info(fields
, troll
)},
104 {disc_copies
, [node()]}
108 % Create all the needed indexes on the tables.
110 % Delete all existing indexes.
111 lists:foreach(fun(T
) ->
112 lists:foreach(fun(P
) ->
113 mnesia:del_table_index(T
, P
)
115 mnesia:table_info(T
, index
)
120 mnesia:add_table_index(minichat
, auteur_id
),
121 mnesia:add_table_index(reponse_minichat
, cible
),
122 mnesia:add_table_index(user
, cookie
),
123 mnesia:add_table_index(user
, login
),
124 mnesia:add_table_index(troll
, date_post
),
125 mnesia:add_table_index(troll
, id_minichat
).
127 % To connect to an remote database node.
132 mnesia:change_config(extra_db_nodes
, [Node
]).
136 mnesia:clear_table(counter
),
137 mnesia:clear_table(proprietes
),
138 mnesia:clear_table(texte
),
139 mnesia:clear_table(user
),
140 mnesia:clear_table(reponse_minichat
),
141 mnesia:clear_table(minichat
),
142 mnesia:clear_table(troll
),
143 mnesia:clear_table(ip_table
),
144 % Create the root user.
145 mnesia:transaction(fun() ->
146 mnesia:write(#proprietes
{nom
= version
, valeur
= ?DB_VERSION
}),
147 User
= #user
{id
= 0, profile
= #profile
{pseudo
= "Sys"}, login
= "Sys", date_creation
= erlang:timestamp(), date_derniere_connexion
= erlang:timestamp(), ek_master
= true
},
154 mnesia:transaction(fun() ->
155 mnesia:write(#texte
{ id
= 10, fr
= "Login déjà existant"}),
156 mnesia:write(#texte
{ id
= 20, fr
= "Trop de register (flood)"}),
157 mnesia:write(#texte
{ id
= 30, fr
= "Couple login/pass introuvable"}),
158 mnesia:write(#texte
{ id
= 40, fr
= "Authentification impossible par cookie"}),
159 mnesia:write(#texte
{ id
= 50, fr
= "Impossible de mettre à jour le profile"}),
160 mnesia:write(#texte
{ id
= 60, fr
= "timeout"}),
161 mnesia:write(#texte
{ id
= 70, fr
= "Page inconnue"}),
162 mnesia:write(#texte
{ id
= 80, fr
= "Vous êtes banni pour encore ~s"}),
163 mnesia:write(#texte
{ id
= 90, fr
= "Message vide"}),
164 mnesia:write(#texte
{ id
= 100, fr
= "Impossible d'ajouter un nouveau message. Cause : ~s"}),
165 mnesia:write(#texte
{ id
= 110, fr
= "Utilisateur inconnu"}),
166 mnesia:write(#texte
{ id
= 120, fr
= "Il n'est pas possible de s'auto bannir"}),
167 mnesia:write(#texte
{ id
= 130, fr
= "L'utilisateur est lui même un ekMaster"}),
168 mnesia:write(#texte
{ id
= 140, fr
= "Utilisateur à bannir inconnu"}),
169 mnesia:write(#texte
{ id
= 150, fr
= "Utilisateur inconnu ou non ek master"}),
170 mnesia:write(#texte
{ id
= 160, fr
= "Utilisateur à slaper inconnu"}),
171 mnesia:write(#texte
{ id
= 170, fr
= "Utilisateur inconnu ou non ek master"}),
172 mnesia:write(#texte
{ id
= 230, fr
= "Seul les ekMaster peuvent connaitre la liste des ips bannies"})
175 % Update the database to the latest version (?DB_VERSION).
177 case mnesia:dirty_read({proprietes
, version
}) of
178 [#proprietes
{valeur
= Version
}] ->
183 update(?DB_VERSION
) -> finished
;
185 case mnesia:backup(backup_file_path(Version
)) of
187 case patch(Version
) of
189 mnesia:dirty_write(#proprietes
{nom
= version
, valeur
= Version
+ 1}),
194 {error
, Message
} -> {error
, lists:flatten(io_lib:format("Error: unable to create a backup file prior updating the database to the version ~w: ~w", [Version
, Message
]))}
198 % Applique une modification de la BD pour passer d'une version à la suivante.
199 % crée un backup avant l'application du patch
200 % dans BD/backups nommé "backup<num>" où <num> et le numéro de la version.
203 % Prend un chemin vers la feuille de style de type "css/1/euphorik.css"
204 % et renvoie "styles/1/euphorik.css"
205 Transforme_css
= fun("css" ++ Reste
) ->
209 Traiter_message
= fun(M
, Racine
) ->
210 F
= fun(F
, M2
) -> % seul moyen à ma connaissance pour faire de la récursion dans une lambda fonction, voir : http://www.nabble.com/Auto-generated-functions-td15279499.html
211 % met à jour la racine de chaque message qui répond à M
214 mnesia:write(M2#minichat
{racine_id
= Racine
}),
217 euphorik_bd:enfants(M#minichat
.id
)
222 % Prend un chemin vers la feuille de style de type "css/1/euphorik.css"
223 % et renvoie "styles/1/euphorik.css"
224 Transforme_css
= fun("css" ++ Reste
) ->
228 Traiter_message
= fun(M
, Racine
) ->
229 F
= fun(F
, M2
) -> % seul moyen à ma connaissance pour faire de la récursion dans une lambda fonction, voir : http://www.nabble.com/Auto-generated-functions-td15279499.html
230 % met à jour la racine de chaque message qui répond à M
233 mnesia:write(M2#minichat
{racine_id
= Racine
}),
236 euphorik_bd:enfants(M#minichat
.id
)
241 mnesia:create_table(texte
, [
242 {attributes
, record_info(fields
, texte
)},
243 {disc_copies
, [node()]}
246 % traitement des users
247 mnesia:transform_table(
249 fun({user
, Id
, Cookie
, Pseudo
, Login
, Password
, Email
, Date_creation
, Date_derniere_connexion
, Css
, Nick_format
, View_times
, View_tooltips
, Indice_flood
, _Page_principale
, Conversations
, Ek_master
, Last_ip
}) ->
250 {user
, Id
, Cookie
, Login
, Password
, {profile
, Pseudo
, Email
, Transforme_css(Css
), Nick_format
, View_times
, View_tooltips
, light
, reverse
, lists:map(fun({R
, _
}) -> {R
, false
} end, Conversations
)}, Date_creation
, Date_derniere_connexion
, Indice_flood
, Ek_master
, Last_ip
}
252 record_info(fields
, user
)
254 mnesia:transform_table(
256 fun({minichat
, Id
, Auteur_id
, Date
, Pseudo
, Contenu
, Troll_id
}) ->
257 {minichat
, Id
, Auteur_id
, Date
, Pseudo
, Contenu
, Troll_id
, Id
}
259 record_info(fields
, minichat
)
261 case mnesia:transaction(
263 % met à jour les enfants des racines
264 % idéalement : utiliser un cursor mais je crois qu'il n'est pas possible de faire des modifs en itérant en même temps avec un cursor, a voir..
265 Messages
= e(q([M
|| M
<- mnesia:table(minichat
), euphorik_bd:parents(M#minichat
.id
) =:= []]), [{tmpdir
, ?KEY_SORT_TEMP_DIR
}]),
266 lists:foreach(fun(M
) -> Traiter_message(M
, M#minichat
.id
) end, Messages
)
269 {aborted
, Cause
} -> {error
, Cause
};
274 mnesia:transform_table(
276 fun({troll
, Id_troll
, Id_user
, Date_create
, Date_post
, Content
}) ->
277 % recherche le message associé s'il existe
278 Id_minichat
= case e(q([M
|| M
<- mnesia:table(minichat
), element(7, M
) =:= Id_troll
]), [{tmpdir
, ?KEY_SORT_TEMP_DIR
}]) of
279 [M
] -> element(2, M
);
282 {troll
, Id_troll
, Id_user
, Id_minichat
, Date_create
, Date_post
, Content
}
284 record_info(fields
, troll
)
286 %mnesia:del_table_index(minichat, troll_id),
287 mnesia:transform_table(
289 fun({minichat
, Id
, Auteur_id
, Date
, Pseudo
, Contenu
, _Troll_id
, Racine_id
}) ->
290 {minichat
, Id
, Auteur_id
, Date
, Pseudo
, Contenu
, Racine_id
, normal
}
292 record_info(fields
, minichat
)
294 mnesia:transaction(fun() ->
295 % comble les trous entre les id non-contigues
296 lists:foreach(fun(Id
) ->
297 case mnesia:read({minichat
, Id
}) of
299 {ok
, #user
{profile
= Profile
}} = euphorik_bd:user_by_id(0),
300 mnesia:write(#minichat
{id
= Id
, auteur_id
= 0, date = undefined
, pseudo
= Profile#profile
.pseudo
, contenu
= "Comblement...", racine_id
= Id
, status
= deleted
});
304 lists:seq(1, mnesia:table_info(minichat
, size))
306 % la table troll utilise maintenant son index et pas celui de la table minichat (correction d'un vieux bug)
307 mnesia:write(#counter
{key
= troll
, value
= mnesia:table_info(minichat
, size)})
309 create_indexes(). % uniquement pour l'indice sur id_minichat de la table troll
312 % Renvoie le dossier dans lequel les backups sont effectué, ce dossier doit être en écriture.
314 mnesia:system_info(directory
) ++ "/backups/".
317 % Renvoie le fichier (avec le chemin) correspondant à la version Version, par exemple : "/var/euphorik/BD/backups/backup1"
318 backup_file_path(Version
) when is_integer(Version
) ->
319 dossier_backups() ++ "backup" ++ integer_to_list(Version
).
322 % crée un backup dont le nom est fournit dans le repertoire backups qui se trouve dans le repertoire de la BD.
324 mnesia:backup(dossier_backups() ++ Fichier
).
327 % Restaure un backup de la BD.
328 restore(Fichier
) when is_list(Fichier
) ->
329 mnesia:restore(dossier_backups() ++ Fichier
, []);
330 % Reviens à une version précédente de la base de données.
331 % (les données insérées durant les versions plus récentes sont perdues).
332 restore(Version
) when is_integer(Version
) ->
333 mnesia:restore(backup_file_path(Version
), []). % [{default_op, recreate_tables}]).
336 % Change le nom du noeud d'un backup.
337 % provient d'ici : http://www.erlang.org/doc/apps/mnesia/Mnesia_chap7.html#6.9
338 % From : l'ancien nom
339 % To : le nouveau nom
340 % Source : le nom du fichier de backup
341 % Target : le nom du fichier du nouveau backup
342 change_node_name(From
, To
, Source
, Target
) ->
344 fun(Node
) when Node
== From
-> To
;
345 (Node
) when Node
== To
-> throw({error
, already_exists
});
349 fun({schema
, db_nodes
, Nodes
}, Acc
) ->
350 {[{schema
, db_nodes
, lists:map(Switch
,Nodes
)}], Acc
};
351 ({schema
, version
, Version
}, Acc
) ->
352 {[{schema
, version
, Version
}], Acc
};
353 ({schema
, cookie
, Cookie
}, Acc
) ->
354 {[{schema
, cookie
, Cookie
}], Acc
};
355 ({schema
, Tab
, CreateList
}, Acc
) ->
356 Keys
= [ram_copies
, disc_copies
, disc_only_copies
],
359 case lists:member(Key
, Keys
) of
360 true
-> {Key
, lists:map(Switch
, Val
)};
364 {[{schema
, Tab
, lists:map(OptSwitch
, CreateList
)}], Acc
};
368 mnesia:traverse_backup(Source
, Target
, Convert
, switched
).
370 toggle_ek_master(User_id
) ->
371 euphorik_bd:resultat_transaction(mnesia:transaction(
373 Users
= e(q([E
|| E
<- mnesia:table(user
), E#user
.id
=:= User_id
]), [{tmpdir
, ?KEY_SORT_TEMP_DIR
}]),
376 mnesia:write(User#user
{ek_master
= not User#user
.ek_master
});
383 % Affiche N user trié par leur date de dernière connexion.
384 % Opt est une liste d'option d'affichage :
385 % * ekmaster : n'affiche que les admins
386 print_users(N
, Opt
) ->
387 AfficheQueLesEkMaster
= lists:any(fun(O
) -> O
=:= ekmaster
end, Opt
),
388 euphorik_bd:resultat_transaction(mnesia:transaction(fun() ->
391 #user
.date_derniere_connexion
,
392 if AfficheQueLesEkMaster
->
393 q([E
|| E
<- mnesia:table(user
), E#user
.ek_master
=:= true
]);
395 q([E
|| E
<- mnesia:table(user
)])
397 [{order
, descending
}]
399 [{tmpdir
, ?KEY_SORT_TEMP_DIR
}]
401 Users
= qlc:next_answers(C
, N
),
412 % Affiche tous les users.
414 print_users(all_remaining
, Opt
).
416 % Affiche tous les users.
418 print_users(all_remaining
, []).
420 print_user(User
) when is_record(User
, user
) ->
421 #user
{id
= Id
, profile
= #profile
{pseudo
= Pseudo
}, login
= Login
, ek_master
= Ek_master
, date_derniere_connexion
= Date
, last_ip
= IP
} = User
,
422 {{Annee
, Mois
, Jour
}, {Heure
, Min
, _
}} = calendar:now_to_local_time(Date
),
424 % id pseudo (login) IP Jour Mois Année Heure Minute
425 "~4w : ~10.10..s(~10.10..s) ~s ~2w.~2.2.0w.~w - ~2wh~2.2.0w~n",
428 if Ek_master
-> "*"; true
-> "" end ++ Pseudo
,
430 euphorik_common:serialize_ip(IP
),
431 Jour
, Mois
, Annee
, Heure
, Min
434 % Affichage d'un user en fonction de son login
435 print_user(Login
) when is_list(Login
) ->
436 case euphorik_bd:user_by_login(Login
) of
440 {error
, "Login pas trouvé : " ++ Login
}
442 % Affichage d'un user en fonction de son id
443 print_user(Id
) when is_integer(Id
) ->
444 case euphorik_bd:user_by_id(Id
) of
448 {error
, "Id pas trouvé : " ++ integer_to_list(Id
)}