2021-02-09 17:37:26 +02:00
/* file-indexer.c
*
* Copyright 2021 Joshua Strobl
*
* Licensed under the Apache License , Version 2.0 ( the " License " ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an " AS IS " BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
*/
# include <dirent.h>
# include <magic.h>
2021-02-16 17:15:10 +02:00
# include <stdio.h>
2021-02-09 17:37:26 +02:00
# include <sys/stat.h>
2021-02-16 17:15:10 +02:00
# include <taglib/tag_c.h>
2021-03-23 19:50:09 +02:00
# include "../db/cartographer.h"
2021-03-09 11:45:44 +02:00
# include "../db/db.h"
2021-05-07 16:45:57 +03:00
# include "../playlist/playlist.h"
2021-03-09 11:45:44 +02:00
# include "../koto-utils.h"
2021-03-23 19:50:09 +02:00
# include "structs.h"
2021-03-09 11:45:44 +02:00
2021-05-11 20:05:04 +03:00
extern KotoCartographer * koto_maps ;
extern sqlite3 * koto_db ;
2021-02-09 17:37:26 +02:00
2021-05-11 20:08:39 +03:00
struct _KotoLibrary {
2021-02-09 17:37:26 +02:00
GObject parent_instance ;
2021-05-27 16:58:28 +03:00
gchar * path ; // Compat
gchar * directory ;
gchar * name ;
KotoLibraryType type ;
gchar * uuid ;
gboolean override_builtin ;
2021-02-09 17:37:26 +02:00
magic_t magic_cookie ;
} ;
2021-05-11 20:08:39 +03:00
G_DEFINE_TYPE ( KotoLibrary , koto_library , G_TYPE_OBJECT ) ;
2021-02-09 17:37:26 +02:00
enum {
PROP_0 ,
PROP_PATH ,
N_PROPERTIES
} ;
2021-05-11 20:05:04 +03:00
static GParamSpec * props [ N_PROPERTIES ] = {
NULL ,
} ;
2021-05-11 20:08:39 +03:00
static void koto_library_get_property (
2021-05-11 20:05:04 +03:00
GObject * obj ,
guint prop_id ,
GValue * val ,
GParamSpec * spec
) ;
2021-05-11 20:08:39 +03:00
static void koto_library_set_property (
2021-05-11 20:05:04 +03:00
GObject * obj ,
guint prop_id ,
const GValue * val ,
GParamSpec * spec
) ;
2021-05-11 20:08:39 +03:00
static void koto_library_class_init ( KotoLibraryClass * c ) {
2021-05-11 20:05:04 +03:00
GObjectClass * gobject_class ;
2021-02-09 17:37:26 +02:00
gobject_class = G_OBJECT_CLASS ( c ) ;
2021-05-11 20:08:39 +03:00
gobject_class - > set_property = koto_library_set_property ;
gobject_class - > get_property = koto_library_get_property ;
2021-02-09 17:37:26 +02:00
props [ PROP_PATH ] = g_param_spec_string (
" path " ,
" Path " ,
" Path to Music " ,
NULL ,
2021-05-11 20:05:04 +03:00
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
2021-02-09 17:37:26 +02:00
) ;
g_object_class_install_properties ( gobject_class , N_PROPERTIES , props ) ;
2021-02-16 17:15:10 +02:00
taglib_id3v2_set_default_text_encoding ( TagLib_ID3v2_UTF8 ) ; // Ensure our id3v2 text encoding is UTF-8
2021-02-09 17:37:26 +02:00
}
2021-05-11 20:08:39 +03:00
static void koto_library_init ( KotoLibrary * self ) {
2021-05-27 16:58:28 +03:00
( void ) self ;
2021-02-16 17:15:10 +02:00
}
2021-05-11 20:08:39 +03:00
static void koto_library_get_property (
2021-05-11 20:05:04 +03:00
GObject * obj ,
guint prop_id ,
GValue * val ,
GParamSpec * spec
) {
2021-05-11 20:08:39 +03:00
KotoLibrary * self = KOTO_LIBRARY ( obj ) ;
2021-05-11 20:05:04 +03:00
2021-02-09 17:37:26 +02:00
switch ( prop_id ) {
case PROP_PATH :
g_value_set_string ( val , self - > path ) ;
break ;
default :
G_OBJECT_WARN_INVALID_PROPERTY_ID ( obj , prop_id , spec ) ;
break ;
}
}
2021-05-11 20:08:39 +03:00
static void koto_library_set_property (
2021-05-11 20:05:04 +03:00
GObject * obj ,
guint prop_id ,
const GValue * val ,
GParamSpec * spec
) {
2021-05-11 20:08:39 +03:00
KotoLibrary * self = KOTO_LIBRARY ( obj ) ;
2021-05-11 20:05:04 +03:00
2021-02-09 17:37:26 +02:00
switch ( prop_id ) {
case PROP_PATH :
2021-05-11 20:08:39 +03:00
koto_library_set_path ( self , g_strdup ( g_value_get_string ( val ) ) ) ;
2021-02-09 17:37:26 +02:00
break ;
default :
G_OBJECT_WARN_INVALID_PROPERTY_ID ( obj , prop_id , spec ) ;
break ;
}
}
2021-05-11 20:08:39 +03:00
void koto_library_set_path (
KotoLibrary * self ,
2021-05-11 20:05:04 +03:00
gchar * path
) {
2021-03-09 11:45:44 +02:00
if ( path = = NULL ) {
return ;
}
if ( self - > path ! = NULL ) {
g_free ( self - > path ) ;
}
self - > path = path ;
if ( created_new_db ) { // Created new database
start_indexing ( self ) ; // Start index operation
} else { // Have existing database
read_from_db ( self ) ; // Read from the database
}
}
2021-05-11 20:05:04 +03:00
int process_artists (
void * data ,
int num_columns ,
char * * fields ,
char * * column_names
) {
2021-05-27 16:58:28 +03:00
( void ) data ;
2021-05-11 20:05:04 +03:00
( void ) num_columns ;
( void ) column_names ; // Don't need any of the params
2021-03-09 11:45:44 +02:00
2021-05-11 20:05:04 +03:00
gchar * artist_uuid = g_strdup ( koto_utils_unquote_string ( fields [ 0 ] ) ) ; // First column is UUID
gchar * artist_path = g_strdup ( koto_utils_unquote_string ( fields [ 1 ] ) ) ; // Second column is path
gchar * artist_name = g_strdup ( koto_utils_unquote_string ( fields [ 3 ] ) ) ; // Fourth column is artist name
2021-03-09 11:45:44 +02:00
2021-05-11 20:08:39 +03:00
KotoArtist * artist = koto_artist_new_with_uuid ( artist_uuid ) ; // Create our artist with the UUID
2021-03-09 11:45:44 +02:00
2021-05-11 20:05:04 +03:00
g_object_set (
artist ,
" path " ,
artist_path , // Set path
" name " ,
artist_name , // Set name
NULL ) ;
2021-03-09 11:45:44 +02:00
2021-03-23 19:50:09 +02:00
koto_cartographer_add_artist ( koto_maps , artist ) ; // Add the artist to our global cartographer
2021-03-09 11:45:44 +02:00
int albums_rc = sqlite3_exec ( koto_db , g_strdup_printf ( " SELECT * FROM albums WHERE artist_id= \" %s \" " , artist_uuid ) , process_albums , artist , NULL ) ; // Process our albums
2021-05-11 20:05:04 +03:00
2021-03-09 11:45:44 +02:00
if ( albums_rc ! = SQLITE_OK ) { // Failed to get our albums
g_critical ( " Failed to read our albums: %s " , sqlite3_errmsg ( koto_db ) ) ;
return 1 ;
}
g_free ( artist_uuid ) ;
g_free ( artist_path ) ;
g_free ( artist_name ) ;
return 0 ;
}
2021-05-11 20:05:04 +03:00
int process_albums (
void * data ,
int num_columns ,
char * * fields ,
char * * column_names
) {
( void ) num_columns ;
( void ) column_names ; // Don't need these
2021-03-09 11:45:44 +02:00
2021-05-11 20:08:39 +03:00
KotoArtist * artist = ( KotoArtist * ) data ;
2021-03-09 11:45:44 +02:00
2021-05-11 20:05:04 +03:00
gchar * album_uuid = g_strdup ( koto_utils_unquote_string ( fields [ 0 ] ) ) ;
gchar * path = g_strdup ( koto_utils_unquote_string ( fields [ 1 ] ) ) ;
gchar * artist_uuid = g_strdup ( koto_utils_unquote_string ( fields [ 2 ] ) ) ;
gchar * album_name = g_strdup ( koto_utils_unquote_string ( fields [ 3 ] ) ) ;
gchar * album_art = ( fields [ 4 ] ! = NULL ) ? g_strdup ( koto_utils_unquote_string ( fields [ 4 ] ) ) : NULL ;
2021-03-09 11:45:44 +02:00
2021-05-11 20:08:39 +03:00
KotoAlbum * album = koto_album_new_with_uuid ( artist , album_uuid ) ; // Create our album
2021-03-09 11:45:44 +02:00
2021-05-11 20:05:04 +03:00
g_object_set (
album ,
" path " ,
path , // Set the path
" name " ,
album_name , // Set name
" art-path " ,
album_art , // Set art path if any
NULL ) ;
2021-03-09 11:45:44 +02:00
2021-03-23 19:50:09 +02:00
koto_cartographer_add_album ( koto_maps , album ) ; // Add the album to our global cartographer
2021-05-11 20:08:39 +03:00
koto_artist_add_album ( artist , album_uuid ) ; // Add the album
2021-03-09 11:45:44 +02:00
int tracks_rc = sqlite3_exec ( koto_db , g_strdup_printf ( " SELECT * FROM tracks WHERE album_id= \" %s \" " , album_uuid ) , process_tracks , album , NULL ) ; // Process our tracks
2021-05-11 20:05:04 +03:00
2021-03-09 11:45:44 +02:00
if ( tracks_rc ! = SQLITE_OK ) { // Failed to get our tracks
g_critical ( " Failed to read our tracks: %s " , sqlite3_errmsg ( koto_db ) ) ;
return 1 ;
}
g_free ( album_uuid ) ;
g_free ( path ) ;
g_free ( artist_uuid ) ;
g_free ( album_name ) ;
if ( album_art ! = NULL ) {
g_free ( album_art ) ;
}
return 0 ;
}
2021-05-11 20:05:04 +03:00
int process_playlists (
void * data ,
int num_columns ,
char * * fields ,
char * * column_names
) {
( void ) data ;
( void ) num_columns ;
( void ) column_names ; // Don't need any of the params
gchar * playlist_uuid = g_strdup ( koto_utils_unquote_string ( fields [ 0 ] ) ) ; // First column is UUID
gchar * playlist_name = g_strdup ( koto_utils_unquote_string ( fields [ 1 ] ) ) ; // Second column is playlist name
gchar * playlist_art_path = g_strdup ( koto_utils_unquote_string ( fields [ 2 ] ) ) ; // Third column is any art path
KotoPlaylist * playlist = koto_playlist_new_with_uuid ( playlist_uuid ) ; // Create a playlist using the existing UUID
2021-05-07 16:45:57 +03:00
koto_playlist_set_name ( playlist , playlist_name ) ; // Add the playlist name
koto_playlist_set_artwork ( playlist , playlist_art_path ) ; // Add the playlist art path
koto_cartographer_add_playlist ( koto_maps , playlist ) ; // Add to cartographer
int playlist_tracks_rc = sqlite3_exec ( koto_db , g_strdup_printf ( " SELECT * FROM playlist_tracks WHERE playlist_id= \" %s \" ORDER BY position ASC " , playlist_uuid ) , process_playlists_tracks , playlist , NULL ) ; // Process our playlist tracks
2021-05-11 20:05:04 +03:00
2021-05-07 16:45:57 +03:00
if ( playlist_tracks_rc ! = SQLITE_OK ) { // Failed to get our playlist tracks
g_critical ( " Failed to read our playlist tracks: %s " , sqlite3_errmsg ( koto_db ) ) ;
return 1 ;
}
koto_playlist_mark_as_finalized ( playlist ) ; // Mark as finalized since loading should be complete
g_free ( playlist_uuid ) ;
g_free ( playlist_name ) ;
g_free ( playlist_art_path ) ;
return 0 ;
}
2021-05-11 20:05:04 +03:00
int process_playlists_tracks (
void * data ,
int num_columns ,
char * * fields ,
char * * column_names
) {
( void ) data ;
( void ) num_columns ;
( void ) column_names ; // Don't need these
gchar * playlist_uuid = g_strdup ( koto_utils_unquote_string ( fields [ 1 ] ) ) ;
gchar * track_uuid = g_strdup ( koto_utils_unquote_string ( fields [ 2 ] ) ) ;
2021-05-07 16:45:57 +03:00
gboolean current = g_strcmp0 ( koto_utils_unquote_string ( fields [ 3 ] ) , " 0 " ) ;
2021-05-11 20:05:04 +03:00
KotoPlaylist * playlist = koto_cartographer_get_playlist_by_uuid ( koto_maps , playlist_uuid ) ; // Get the playlist
2021-05-11 20:08:39 +03:00
KotoTrack * track = koto_cartographer_get_track_by_uuid ( koto_maps , track_uuid ) ; // Get the track
2021-05-11 20:05:04 +03:00
2021-05-07 16:45:57 +03:00
if ( ! KOTO_IS_PLAYLIST ( playlist ) ) {
goto freeforret ;
}
koto_playlist_add_track ( playlist , track , current , FALSE ) ; // Add the track to the playlist but don't re-commit to the table
freeforret :
g_free ( playlist_uuid ) ;
g_free ( track_uuid ) ;
return 0 ;
}
2021-05-11 20:05:04 +03:00
int process_tracks (
void * data ,
int num_columns ,
char * * fields ,
char * * column_names
) {
( void ) num_columns ;
( void ) column_names ; // Don't need these
2021-05-11 20:08:39 +03:00
KotoAlbum * album = ( KotoAlbum * ) data ;
2021-05-11 20:05:04 +03:00
gchar * track_uuid = g_strdup ( koto_utils_unquote_string ( fields [ 0 ] ) ) ;
gchar * path = g_strdup ( koto_utils_unquote_string ( fields [ 1 ] ) ) ;
gchar * artist_uuid = g_strdup ( koto_utils_unquote_string ( fields [ 3 ] ) ) ;
gchar * album_uuid = g_strdup ( koto_utils_unquote_string ( fields [ 4 ] ) ) ;
gchar * file_name = g_strdup ( koto_utils_unquote_string ( fields [ 5 ] ) ) ;
gchar * name = g_strdup ( koto_utils_unquote_string ( fields [ 6 ] ) ) ;
guint * disc_num = ( guint * ) g_ascii_strtoull ( fields [ 7 ] , NULL , 10 ) ;
guint * position = ( guint * ) g_ascii_strtoull ( fields [ 8 ] , NULL , 10 ) ;
2021-05-11 20:08:39 +03:00
KotoTrack * track = koto_track_new_with_uuid ( track_uuid ) ; // Create our file
2021-05-11 20:05:04 +03:00
g_object_set ( track , " artist-uuid " , artist_uuid , " album-uuid " , album_uuid , " path " , path , " file-name " , file_name , " parsed-name " , name , " cd " , disc_num , " position " , position , NULL ) ;
2021-03-09 11:45:44 +02:00
2021-05-11 20:08:39 +03:00
koto_album_add_track ( album , track ) ; // Add the track
2021-03-09 11:45:44 +02:00
g_free ( track_uuid ) ;
g_free ( path ) ;
g_free ( artist_uuid ) ;
g_free ( album_uuid ) ;
g_free ( file_name ) ;
g_free ( name ) ;
return 0 ;
}
2021-05-11 20:08:39 +03:00
void read_from_db ( KotoLibrary * self ) {
2021-03-09 11:45:44 +02:00
int artists_rc = sqlite3_exec ( koto_db , " SELECT * FROM artists " , process_artists , self , NULL ) ; // Process our artists
2021-05-11 20:05:04 +03:00
2021-03-09 11:45:44 +02:00
if ( artists_rc ! = SQLITE_OK ) { // Failed to get our artists
g_critical ( " Failed to read our artists: %s " , sqlite3_errmsg ( koto_db ) ) ;
return ;
}
2021-05-07 16:45:57 +03:00
int playlist_rc = sqlite3_exec ( koto_db , " SELECT * FROM playlist_meta " , process_playlists , self , NULL ) ; // Process our playlists
2021-05-11 20:05:04 +03:00
2021-05-07 16:45:57 +03:00
if ( playlist_rc ! = SQLITE_OK ) { // Failed to get our playlists
g_critical ( " Failed to read our playlists: %s " , sqlite3_errmsg ( koto_db ) ) ;
return ;
}
2021-03-09 11:45:44 +02:00
}
2021-05-11 20:08:39 +03:00
void start_indexing ( KotoLibrary * self ) {
2021-02-09 17:37:26 +02:00
struct stat library_stat ;
int success = stat ( self - > path , & library_stat ) ;
if ( success ! = 0 ) { // Failed to read the library path
return ;
}
if ( ! S_ISDIR ( library_stat . st_mode ) ) { // Is not a directory
g_warning ( " %s is not a directory " , self - > path ) ;
return ;
}
self - > magic_cookie = magic_open ( MAGIC_MIME ) ;
if ( self - > magic_cookie = = NULL ) {
return ;
}
if ( magic_load ( self - > magic_cookie , NULL ) ! = 0 ) {
magic_close ( self - > magic_cookie ) ;
return ;
}
2021-02-16 17:15:10 +02:00
index_folder ( self , self - > path , 0 ) ;
2021-02-09 17:37:26 +02:00
magic_close ( self - > magic_cookie ) ;
}
2021-05-11 20:08:39 +03:00
KotoLibrary * koto_library_new ( const gchar * path ) {
2021-05-11 20:05:04 +03:00
return g_object_new (
2021-05-11 20:08:39 +03:00
KOTO_TYPE_LIBRARY ,
2021-05-11 20:05:04 +03:00
" path " ,
path ,
2021-03-09 11:45:44 +02:00
NULL
) ;
}
2021-05-11 20:05:04 +03:00
void index_folder (
2021-05-11 20:08:39 +03:00
KotoLibrary * self ,
2021-05-11 20:05:04 +03:00
gchar * path ,
guint depth
) {
2021-02-16 17:15:10 +02:00
depth + + ;
2021-05-11 20:05:04 +03:00
DIR * dir = opendir ( path ) ; // Attempt to open our directory
2021-02-09 17:37:26 +02:00
if ( dir = = NULL ) {
return ;
}
2021-05-11 20:05:04 +03:00
struct dirent * entry ;
2021-02-09 17:37:26 +02:00
while ( ( entry = readdir ( dir ) ) ) {
2021-02-16 17:15:10 +02:00
if ( g_str_has_prefix ( entry - > d_name , " . " ) ) { // A reference to parent dir, self, or a hidden item
continue ;
}
2021-02-09 17:37:26 +02:00
2021-05-11 20:05:04 +03:00
gchar * full_path = g_strdup_printf ( " %s%s%s " , path , G_DIR_SEPARATOR_S , entry - > d_name ) ;
2021-02-09 17:37:26 +02:00
2021-02-16 17:15:10 +02:00
if ( entry - > d_type = = DT_DIR ) { // Directory
if ( depth = = 1 ) { // If we are following FOLDER/ARTIST/ALBUM then this would be artist
2021-05-11 20:08:39 +03:00
KotoArtist * artist = koto_artist_new ( full_path ) ; // Attempt to get the artist
2021-05-11 20:05:04 +03:00
gchar * artist_name ;
2021-02-09 17:37:26 +02:00
2021-05-11 20:05:04 +03:00
g_object_get (
artist ,
" name " ,
& artist_name ,
2021-02-16 17:15:10 +02:00
NULL
) ;
2021-02-09 17:37:26 +02:00
2021-03-23 19:50:09 +02:00
koto_cartographer_add_artist ( koto_maps , artist ) ; // Add the artist to cartographer
2021-02-16 17:15:10 +02:00
index_folder ( self , full_path , depth ) ; // Index this directory
g_free ( artist_name ) ;
} else if ( depth = = 2 ) { // If we are following FOLDER/ARTIST/ALBUM then this would be album
2021-05-11 20:05:04 +03:00
gchar * artist_name = g_path_get_basename ( path ) ; // Get the last entry from our path which is probably the artist
2021-05-27 16:58:28 +03:00
KotoArtist * artist = koto_cartographer_get_artist_by_name ( koto_maps , artist_name ) ;
2021-02-09 17:37:26 +02:00
2021-05-27 16:58:28 +03:00
if ( ! KOTO_IS_ARTIST ( artist ) ) { // Not an artist
2021-02-16 17:15:10 +02:00
continue ;
}
2021-05-11 20:08:39 +03:00
KotoAlbum * album = koto_album_new ( artist , full_path ) ;
2021-03-23 19:50:09 +02:00
koto_cartographer_add_album ( koto_maps , album ) ; // Add our album to the cartographer
2021-03-02 19:12:12 +02:00
2021-05-11 20:05:04 +03:00
gchar * album_uuid = NULL ;
2021-03-23 19:50:09 +02:00
g_object_get ( album , " uuid " , & album_uuid , NULL ) ;
2021-05-11 20:08:39 +03:00
koto_artist_add_album ( artist , album_uuid ) ; // Add the album
2021-02-16 17:15:10 +02:00
g_free ( artist_name ) ;
}
}
2021-02-09 17:37:26 +02:00
2021-02-16 17:15:10 +02:00
g_free ( full_path ) ;
2021-02-09 17:37:26 +02:00
}
2021-02-16 17:15:10 +02:00
closedir ( dir ) ; // Close the directory
2021-05-27 16:58:28 +03:00
}