
Valhalla: les nouvelles fonctions de sélection
2009.05.31Hello,
depuis peu, les modifications évoquées dans le billet précédent ont été réalisées. L’API a été modifiée pour toutes les fonctions qui permettent d’effectuer des sélections sur la base de donnée. Ce blog étant aussi en quelque sorte un carnet de laboratoire, je vais donc y expliquer le fonctionnement de ces fonctions ainsi que la manière de les utiliser. A terme, ces informations figureront directement dans la documentation (en anglais) de libvalhalla. Il est judicieux de consulter la documentation Doxygen déjà présente dans l’en-tête public (valhalla.h) qui peut être généré via un paramètre du script “configure”.
Cet article est long et très spécifique à libvalhalla du côté développeur uniquement. Si vous êtes un simple utilisateur d’Enna, vous ne verrez pas vraiment de différence par rapport à avant car les modifications sont majoritairement internes.
Il y a trois fonctions publiques et chacune est complémentaire aux autres.
int valhalla_db_metalist_get (
valhalla_t *handle,
valhalla_db_item_t *search,
valhalla_db_restrict_t *restriction,
int (*result_cb) (void *data,
valhalla_db_metares_t *res),
void *data);
int valhalla_db_filelist_get (
valhalla_t *handle,
valhalla_file_type_t filetype,
valhalla_db_restrict_t *restriction,
int (*result_cb) (void *data,
valhalla_db_fileres_t *res),
void *data);
int valhalla_db_file_get (
valhalla_t *handle,
int64_t id, const char *path,
valhalla_db_restrict_t *restriction,
valhalla_db_filemeta_t **res);

valhalla_db_metalist_get
Utilisation simple
Elle permet de récupérer une liste de metadata. Cela veut dire, par exemple une liste d’albums, d’auteurs, d’artistes, de genres; peu importe le nom, vous pouvez récupérer n’importe quelle liste. Le résultat se présente toujours sous la forme du nom du champ (comme “author”) avec une valeur et un groupe. Sous la forme d’un tableau, cela donnerait quelque chose comme:
| meta id | meta name | data id | data value | group |
| 2 | author | 1665 | Dido | ENTITIES |
| 2 | author | 456 | Ennio Morricone | ENTITIES |
| 2 | author | 1257 | John Williams | ENTITIES |
| 2 | author | 1224 | Linkin Park | ENTITIES |
Dans cet exemple, uniquement les auteurs sont présents, mais il est également possible de demander une liste de metadata selon un groupe et non selon un nom de meta ou un id. Vous pouvez typiquement demander la liste de toutes les metadata qui appartiennent au group ENTITIES. De ce fait, vous trouverez la liste de tous les auteurs, artistes, compositeurs, studios, etc,…
Sans rentrer encore dans les détails du code, une telle liste se récupère de cette manière:
valhalla_db_item_t s = VALHALLA_DB_SEARCH_TEXT ("author", ENTITIES);
valhalla_db_metalist_get (handle, &s, NULL, callback, NULL);
Et sans expliquer en détail la fonction, l’argument “handle” correspond au pointeur sur votre instance de valhalla, “s” (pour search) défini ce que vous cherchez, le troisième argument permet de spécifier des restrictions (qui sont expliquées dans le chapitre suivant), le callback est la fonction dans laquelle chaque ligne du résultat est envoyée et le dernier argument est un pointeur void qui peut être récupéré dans le callback.
Les macros VALHALLA_DB_SEARCH_XXX sont présentes pour simplifier l’utilisation de libvalhalla. Elles ne sont pas indispensables. En réalité, elle ne font qu’attribuer les valeurs au bon endroit dans la structure valhalla_db_item_t. Actuellement il existe trois macros différentes:
VALHALLA_DB_SEARCH_ID(meta_id, group)
VALHALLA_DB_SEARCH_TEXT(meta_text, group)
VALHALLA_DB_SEARCH_GRP(group)
Vous pouvez donc chercher une liste de meta en fonction de l’id, du texte, ou alors vous pouvez récupérer plusieurs noms de metadata différents dans le cas d’une recherche selon un groupe.
Utilisation étendue
Récupérer une liste comme expliqué dans le chapitre précédent est la fonctionnalité la plus simple offerte par cette fonction. Il va de soit qu’il faut pouvoir donner quelques restrictions sur la liste retournée. Il est courant de ne pas vouloir une liste complète. Un exemple parlant concerne la metadata “album”. Il est intéressant de pouvoir récupérer tous les albums d’un auteur en particulier. Et c’est ici qu’interviennent les restrictions. Elles permettent d’inclure ou d’exclure certains résultats. D’un point de vue SQL, se sont des sous-requêtes qui limitent les résultats à leur ensemble, ou alors à tous les résultats différents de leur ensemble.
Voici un premier exemple. Vous voulez tous les albums de Dido:
valhalla_db_item_t s = VALHALLA_DB_SEARCH_TEXT ("album", TITLES);
valhalla_db_restrict_t r1 = VALHALLA_DB_RESTRICT_STR (IN, "author", "Dido");
valhalla_db_metalist_get (handle, &s, &r1, callback, NULL);
Le résultat sera un tableau de la même forme que vue précédemment, mais qui contiendra uniquement les albums de Dido.
| meta id | meta name | data id | data value | group |
| 3 | album | 1666 | Life For Rent | TITLES |
| 3 | album | 1669 | No Angel | TITLES |
| 3 | album | 1725 | Safe Trip Home | TITLES |
Cette requête est déjà bien plus sophistiquée, car vous pouvez imaginer de donner ce genre de restriction pour n’importe quel metadata. Un autre exemple, vous pourriez lister tous les albums d’une certaine année. Il n’y a aucune limite sur les choix des restrictions. Mais ceci n’est pas suffisant, et il y a donc des possibilités un peu plus complexes afin d’avoir des résultats plus spécifiques.
Tout d’abord, dans l’exemple précédent, la macro VALHALLA_DB_RESTRICT est utilisée avec un premier argument “IN”. Il est possible d’inverser la logique en y inscrivant “NOTIN” à la place. Le résultat deviendrait alors la liste de tous les albums qui existent dans la base de donnée, sauf ceux de Dido.
Utilisation avancée
L’utilisation étendue permet déjà d’effectuer de nombreuse recherches intéressantes. Néanmoins pour les cas les plus précis, elle ne suffit pas. Il est nécessaire parfois de cumuler les restrictions pour affiner le résultats, ou tout simplement, pour avoir des résultats un peu plus exotiques. Ainsi les restrictions sont des listes chaînées, et il est possible de les lier très facilement. Commençons par un exemple, nous désirons la liste de tous les albums de Dido qui ne sont pas de 2003.
valhalla_db_item_t s = VALHALLA_DB_SEARCH_TEXT ("album", TITLES);
valhalla_db_restrict_t r1 = VALHALLA_DB_RESTRICT_STR (IN, "author", "Dido");
valhalla_db_restrict_t r2 = VALHALLA_DB_RESTRICT_STR (NOTIN, "year", "2003");
VALHALLA_DB_RESTRICT_LINK (r2, r1);
valhalla_db_metalist_get (handle, &s, &r1, callback, NULL);
Une nouvelle macro est donc introduite. Elle permet de lier très simplement les restrictions ensemble. La macro doit être comprise de cette manière. “link from r2 to r1″, soit lier de r2 à r1. Ainsi c’est r1 qui doit être spécifié dans la fonction valhalla_db_metalist_get(). Il n’y a aucune limite sur le nombre de liens possibles (mise à part qu’une requête ne doit pas dépasser 4096 caractères), néanmoins il ne faut pas tenter de lier toutes les restrictions sur la même car seul le dernier lien sera considéré. Avec trois restrictions, la bonne manière de procéder est donc:
valhalla_db_item_t s = VALHALLA_DB_SEARCH_TEXT ("album", TITLES);
valhalla_db_restrict_t r1 = VALHALLA_DB_RESTRICT_STR (IN, "author", "Linkin Park");
valhalla_db_restrict_t r2 = VALHALLA_DB_RESTRICT_STR (NOTIN, "year", "2003");
valhalla_db_restrict_t r3 = VALHALLA_DB_RESTRICT_STR (NOTIN, "genre", "Alternative");
VALHALLA_DB_RESTRICT_LINK (r3, r2);
VALHALLA_DB_RESTRICT_LINK (r2, r1);
valhalla_db_metalist_get (handle, &s, &r1, callback, NULL);
Ici, la liste concernera donc tous les albums de Linkin Park, qui ne sont pas de 2003 et qui ne sont pas du rock/metal alternatif. Okay, la liste ne risque pas d’être bien longue avec cet exemple :-P, mais ce qui compte c’est de montrer le principe. Les restrictions sont liées de la manière suivante: de r3 à r2 et de r2 à r1. En code ça donnerait donc:
r2.next = &r3; r1.next = &r2;
Le code ci-dessus peut très bien remplacer la macro si vous préférez.
Il y a encore une macro que je n’ai pas vraiment présenté. Pour l’argument de recherche de la fonction, il est possible d’indiquer uniquement un groupe (comme je l’ai mentionné plus haut dans ce billet). Si le groupe est mit à NIL, il y aura toutes les metadata qui seront listées, autrement il n’y aura que les metadata liées au groupe.
Un dernier exemple qui retourne absolument toutes les metadata liées à Dido pour son album Life For Rent.
valhalla_db_item_t s = VALHALLA_DB_SEARCH_GRP (NIL); valhalla_db_restrict_t r1 = VALHALLA_DB_RESTRICT_STR (IN, "author", "Dido"); valhalla_db_restrict_t r2 = VALHALLA_DB_RESTRICT_STR (IN, "album", "Life For Rent"); VALHALLA_DB_RESTRICT_LINK (r2, r1); valhalla_db_metalist_get (handle, &s, &r1, callback, NULL);
| meta id | meta name | data id | data value | group |
| 6 | track | 19 | 1 | ORGANIZATIONAL |
| 6 | track | 225 | 10 | ORGANIZATIONAL |
| 6 | track | 229 | 11 | ORGANIZATIONAL |
| 6 | track | 242 | 12 | ORGANIZATIONAL |
| 6 | track | 92 | 2 | ORGANIZATIONAL |
| 5 | year | 50 | 2003 | TEMPORAL |
| 6 | track | 202 | 3 | ORGANIZATIONAL |
| 6 | track | 101 | 4 | ORGANIZATIONAL |
| 6 | track | 223 | 5 | ORGANIZATIONAL |
| 6 | track | 96 | 6 | ORGANIZATIONAL |
| 6 | track | 231 | 7 | ORGANIZATIONAL |
| 6 | track | 196 | 8 | ORGANIZATIONAL |
| 6 | track | 187 | 9 | ORGANIZATIONAL |
| 1 | title | 1673 | BonusTrack | TITLES |
| 2 | author | 1665 | Dido | ENTITIES |
| 1 | title | 1668 | Do You Have A Little Time | TITLES |
| 1 | title | 1672 | Dont Leave Home | TITLES |
| 1 | title | 1666 | Life For Rent | TITLES |
| 3 | album | 1666 | Life For Rent | TITLES |
| 1 | title | 1678 | Marys In India | TITLES |
| 4 | genre | 104 | Pop | CLASSIFICATION |
| 1 | title | 1670 | Sand In My Shoes | TITLES |
| 1 | title | 1669 | See The Sun | TITLES |
| 1 | title | 1674 | See You When Youre 40 | TITLES |
| 1 | title | 1675 | Stoned | TITLES |
| 1 | title | 1671 | This Land Is Mine | TITLES |
| 1 | title | 1664 | White Flag | TITLES |
| 1 | title | 1667 | Who Makes You Feel | TITLES |
Et d’un point de vue SQL
La requête SQL qui effectue les sélections est créée en fonction des arguments de la fonction. Elle se construit comme des lego qui s’imbriquent les uns dans les autres. D’une manière général, sa forme est:
SELECT meta.meta_id, data.data_id, meta.meta_name, data.data_value
FROM (
data INNER JOIN assoc_file_metadata AS assoc
ON data.data_id = assoc.data_id
) INNER JOIN meta
ON assoc.meta_id = meta.meta_id
-- Les conditions sont optionnelles
WHERE
-- Une sous-requête apparait par le biais des restrictions.
-- Il y a autant de sous-requêtes que de restrictions du type IN, NOT IN.
assoc.file_id <IN|NOT IN> (
SELECT assoc.file_id
FROM (
data INNER JOIN assoc_file_metadata AS assoc
ON data.data_id = assoc.data_id
) INNER JOIN meta
ON assoc.meta_id = meta.meta_id
-- A choix, il peut y avoir un ID ou du texte.
WHERE meta.<meta_id|meta_name> = <ID|"TEXT"> AND data.<data_id|data_value> = <ID|"TEXT">
)
-- L'argument search indique quel meta et/ou quel groupe doit être listé
AND meta.<meta_id|meta_name> = <ID|"TEXT">
AND assoc._grp_id = <ID>
GROUP BY assoc.meta_id, assoc.data_id
ORDER BY data.data_value;
Sa longueur varie fortement selon le nombre de conditions. Il est possible de visualiser les requêtes générées par Valhalla en utilisant la verbosité maximale de la bibliothèque.
valhalla_db_filelist_get
Utilisation simple
La récupération d’une liste de fichiers ressemble à celle pour les metadata. La différence vient principalement du deuxième argument qui fait référence au type de fichier (audio, video, etc, …). Ce qui limite ensuite la liste se sont les restrictions. L’utilisation étant exactement la même, je vais aller droit au but avec des exemples.
Pour lister la totalité des fichiers référencés dans la base de donnée, rien de plus simple:
valhalla_db_filelist_get (handle, VALHALLA_FILE_TYPE_NULL, NULL, callback, NULL);
Le fait d’indiquer un type de fichier NULL, permet d’ignorer le type. Le résultat de cette requête correspond à un tableau de ce genre qui contiendrait la totalité de l’arborescence:
| id | path | filetype |
| … | … | … |
| 101 | /home/foo/bar/dido/life_for_rent/sand_in_my_shoes.m4a | AUDIO |
| 102 | /home/foo/bar/dido/life_for_rent/white_flag.m4a | AUDIO |
| 103 | /home/foo/bar/dido/life_for_rent/who_makes_you_feel.m4a | AUDIO |
| … | … | … |
Avec les restrictions
Il est ensuite facile de faire du tri avec les restrictions, de la même manière que pour la fonction précédente. Par exemple, pour lister tous les fichiers d’Ennio Morricone qui ne sont référencés dans aucun album.
valhalla_db_restrict_t r1 = VALHALLA_DB_RESTRICT_STR (IN, "author", "Ennio Morricone"); valhalla_db_restrict_t r2 = VALHALLA_DB_RESTRICT_STR (NOTIN, "album", NULL); VALHALLA_DB_RESTRICT_LINK (r2, r1); valhalla_db_filelist_get (handle, VALHALLA_FILE_TYPE_NULL, &r1, callback, NULL);
Et ci-dessous, un autre exemple qui correspond à la catégorie “Non Classé” dans Enna. Tous les fichiers qui ne sont ni dans un album, ni lié à un author et dont le genre est indéfini.
valhalla_db_restrict_t r1 = VALHALLA_DB_RESTRICT_STR (NOTIN, "album", NULL); valhalla_db_restrict_t r2 = VALHALLA_DB_RESTRICT_STR (NOTIN, "author", NULL); valhalla_db_restrict_t r3 = VALHALLA_DB_RESTRICT_STR (NOTIN, "genre", NULL); VALHALLA_DB_RESTRICT_LINK (r3, r2); VALHALLA_DB_RESTRICT_LINK (r2, r1); valhalla_db_filelist_get (handle, VALHALLA_FILE_TYPE_NULL, &r1, callback, NULL);
Facile, non?
A noter que ce n’est pas encore totalement optimal, car le groupe ne peut pas être donné avec les restrictions. Ainsi n’importe quel metadata de n’importe quel groupe est considérée dans l’inclusion comme l’exclusion.
Et d’un point de vue SQL
La aussi, la requête ressemble beaucoup à la précédente avec des différences au niveau de ce qui est sélectionné et dans quelle table.
SELECT file_id, file_path, _type_id
FROM file AS assoc
-- Les conditions sont optionnelles
WHERE
-- Une sous-requête apparait par le biais des restrictions.
-- Il y a autant de sous-requêtes que de restrictions du type IN, NOT IN.
assoc.file_id <IN|NOT IN> (
SELECT assoc.file_id
FROM (
data INNER JOIN assoc_file_metadata AS assoc
ON data.data_id = assoc.data_id
) INNER JOIN meta
ON assoc.meta_id = meta.meta_id
-- A choix, il peut y avoir un ID ou du texte.
WHERE meta.<meta_id|meta_name> = <ID|"TEXT"> AND data.<data_id|data_value> = <ID|"TEXT">
)
-- Le type n'est pas inscrit dans la requête si la valeur est NULL.
AND _type_id = <ID>
ORDER BY file_id;
Si dans la clause FROM, la table “file” créer un alias “assoc”, c’est uniquement pour une question de factorisation dans le code par rapport à la sous-requête.
valhalla_db_file_get
Maintenant que l’on a vu comment récupérer des listes de metadata ainsi que des listes de fichiers, il est important de pouvoir aussi récupérer des metadata en fonction d’un fichier spécifique. Cette possibilité n’existait pas avec l’ancien modèle car les fichiers étaient toujours retournés dans une liste. Ici il est question d’un seul fichier à la fois. Il faut également être conscient que cette fonction est liée uniquement à la base de donnée, si un fichier non référencé dans la base mais existant quelque part est donné en argument, aucun résultat ne sera retourné (cette possibilité existera quand j’implémenterai le “scan on-demand”).
Les arguments sont donc l’id du fichier, le chemin du fichier, des restrictions et le pointeur sur la structure de résultat. Il n’y a pas de callback pour l’utilisateur car le résultat est limité à un seul fichier (en réalité il y a un callback interne qui récupère toutes les metadata car le vrai résultat est sur plusieurs lignes).
Les metadata sont disponibles dans une structure chaînée. Je dois également introduire une nouvelle possibilité des restrictions. Mais tout d’abord, voici un exemple:
valhalla_db_filemeta_t *metadata = NULL; valhalla_db_restrict_t r1 = VALHALLA_DB_RESTRICT_STR (EQUAL, "track", NULL); valhalla_db_restrict_t r2 = VALHALLA_DB_RESTRICT_STR (EQUAL, "title", NULL); VALHALLA_DB_RESTRICT_LINK (r2, r1); valhalla_db_file_get (handle, 0, "/home/foo/bar/dido/life_for_rent/white_flag.m4a", &r1, &metadata); /* ... */ VALHALLA_DB_FILEMETA_FREE (metadata);
L’argument EQUAL dans la macro VALHALLA_DB_RESTRICT sert à limiter les résultats à des metadata précises. Sans les restrictions, la structure metadata contiendrait absolument toutes les metadata. Mais dans cet exemple, la structure contient uniquement le track et le titre. A noter qu’utiliser IN ou NOTIN ici ça n’a aucun sens. De même qu’utiliser EQUAL pour les deux fonctions précédentes (voir la conclusion).
Il faut être conscient que plusieurs tracks et plusieurs titres peuvent être retournés pour un même fichier. Car rien n’exclus que certains noms de metadata n’existent pas dans différents groupes. Et comme je l’ai dis plus haut, il n’est pour le moment pas possible de limiter une restriction à un groupe. Néanmoins le groupe est un champ de la structure valhalla_db_filemeta_t, il est donc possible de faire un tri à postériori.
Après utilisation des metadata, il ne faut pas oublier de les libérer avec VALHALLA_DB_FILEMETA_FREE().
Et d’un point de vue SQL
SELECT file.file_id, assoc._grp_id, meta.meta_id, data.data_id, meta.meta_name, data.data_value
FROM ((
file INNER JOIN assoc_file_metadata AS assoc
ON file.file_id = assoc.file_id
) INNER JOIN data
ON data.data_id = assoc.data_id
) INNER JOIN meta
ON assoc.meta_id = meta.meta_id
-- Les conditions sont optionnelles
WHERE
-- Une condition apparait par le biais des restrictions.
-- Il y a autant de conditions que de restrictions du type EQUAL.
-- A choix, il peut y avoir un ID ou du texte. Chaque restriction
-- est séparée par un OR.
(
(meta.<meta_id|meta_name> = <ID|"TEXT"> AND data.<data_id|data_value> = <ID|"TEXT">)
<OR> ...
)
-- Il faut l'id d'un fichier ou un path
AND file.<file_id|file_path> = <ID|"PATH">
;
Cette requête retourne autant de lignes qu’il y a de metadata. Mais pour l’utilisateur, tout est transparent car les résultats sont tous retournés dans une structure chaînée.
En conclusion
Les possibilités de ce nouveau système sont très larges par rapport à l’ancien. Bien qu’il y ait encore quelques manques pour pouvoir spécifier des groupes avec les restrictions, il est possible de retrouver les données avec peu de lignes de code et simplement, tout en ayant des requêtes relativement complexes qui se créer en interne. Il y aura forcément des modifications à l’avenir, je tâcherais de garder à jour cet article.
Et pour en revenir aux restrictions et à l’argument qui peut prendre la valeur IN, NOTIN ou EQUAL, il faut comprendre que c’est avant tout une question de factorisation en interne. IN et NOTIN devraient être utilisés uniquement avec les deux premières fonctions présentées qui récupèrent des listes, et EQUAL devrait être utilisé uniquement pour la récupération des metadata d’un fichier. Je vais continuer à travailler sur ces éléments dans les jours à venir afin d’éviter des confusions.
A bientôt,
Mathieu SCHROETER