Skip to content
Extraits de code Groupes Projets
Valider 32edaf2f rédigé par Kubat's avatar Kubat
Parcourir les fichiers

Adding interactions with the database for the user.

parent bd3f3a0a
Branches
Étiquettes
1 requête de fusion!6Resolve "Liste des commandes"
Affichage de avec 1153 ajouts et 423 suppressions
......@@ -2,3 +2,39 @@
A Karaoke player made to replace the old bash scripts on Sakura.
They will enable one to have a database and make requests over the network.
## Building the player
Prerequisites:
- GNU make
- a C compiler with C11 support (e.g. `gcc>4.6`, `clang>3.1`)
- a POSIX.1-2008 compatible system (i.e. not MS Windows)
```
cd player
make
```
Run lektor with `bin/player.out`.
## Preparing a kara for lektor
As a karamaker, you need to add necessary information to your kara so that
lektor can store it in its database. This information is to be directly
written in the `.mkv` file.
For that purpose, run the python script `karadata.py` from the command-line,
with your kara as an argument, like so:
```
./karadata.py /path/to/my/kara.mkv
```
The script will ask you several questions, and then write your answers in the
video file.
Consecutive runs of `karadata.py` on the same kara will overwrite all
meta-information that was previously on the `.mkv` file. It means you can
re-run the script on the same kara to add corrections.
......@@ -48,7 +48,7 @@ INSERT INTO language (name) VALUES
-- M3U except it's stored in a SQL database.
CREATE TABLE IF NOT EXISTS playlist
( id INTEGER PRIMARY KEY
( id INTEGER PRIMARY KEY AUTOINCREMENT
, name TEXT NOT NULL UNIQUE
);
......@@ -65,7 +65,7 @@ CREATE TABLE IF NOT EXISTS kara_playlist
-- https://www.musicpd.org/doc/html/user.html#the-queue
CREATE TABLE IF NOT EXISTS queue
( position INTEGER PRIMARY KEY
( position INTEGER PRIMARY KEY AUTOINCREMENT
, kara_id INTEGER REFERENCES kara
);
......@@ -102,3 +102,12 @@ CREATE TABLE IF NOT EXISTS misc
);
INSERT INTO misc (id) VALUES (42);
-- A simple view to concatenate informations about karas to make search on them
CREATE VIEW IF NOT EXISTS view_kara AS
SELECT
id,
(category || '/' || source_name || ' - ' || song_type || ' - ' || song_name || ' [' || author_name || ' ' || author_year || ']') AS string
FROM kara
;
#!/usr/bin/env python3
import os
import subprocess as proc
import sys
import re
def prompt(text):
while True:
res = input(f"[{text}]> ").strip()
if len(res) > 0:
return res
def prompt_secure(text, possible_list):
while True:
res = input(f"[{text}] in {possible_list}> ").strip()
if len(res) > 0 and res in possible_list:
return res
def confirm(text):
return input(text + " [y/N] ").strip().lower() == "y"
def has_exe(name):
return proc.call(["which", name], stdout=proc.DEVNULL, stderr=proc.DEVNULL) == 0
def mkvpropedit(mkvfile, source, song, category, lang, author, song_type):
TMP_FILE_PATH = "/tmp/meirlmdrsavalenomdefichier.tmp"
edits = f"""
<?xml version="1.0" encoding="UTF-8"?>
<Tags>
<Tag>
<Targets>
<TargetTypeValue>70</TargetTypeValue>
</Targets>
<Simple>
<Name>TITLE</Name>
<String>{source}</String>
</Simple>
</Tag>
<Tag>
<Simple>
<Name>TITLE</Name>
<String>{song}</String>
</Simple>
<Simple>
<Name>CONTENT_TYPE</Name>
<String>{category}</String>
</Simple>
<Simple>
<Name>ADDRESS</Name>
<String>{lang}</String>
</Simple>
<Simple>
<Name>ARTIST</Name>
<String>{author}</String>
</Simple>
<Simple>
<Name>GENRE</Name>
<String>{song_type}</String>
</Simple>
</Tag>
</Tags>
"""
with open(TMP_FILE_PATH, "w") as f:
print(edits, file=f)
args = ["mkvpropedit", "-t", "all:{}".format(TMP_FILE_PATH), mkvfile]
res = proc.call(args, stdout=proc.DEVNULL)
os.remove(TMP_FILE_PATH)
return res
def interactive():
print("Prêt à enregister les infos de ton kara ?\nTous les champs son obligatoires.\n")
source_name = prompt("Titre de l'œuvre dont est issu ce kara")
song_name = prompt("Nom de la chanson/musique ")
category = prompt_secure("Catégorie du kara", ["va", "vo", "amv", "vocaloid", "cdg", "autre"])
song_type = prompt_secure("Type du kara", ['OP', 'ED', 'IS', 'AMV', 'LIVE', 'MP', 'MV', 'VOCA', 'CDG'])
language = prompt_secure("Langue principale dans le kara", ["jp", "en", "fr", "ru", "it", "sp", "ch", "multi", "undefined"])
author_name = prompt("Ton pseudo")
print("Titre de l'œuvre :", source_name)
print("Nom de la chanson :", song_name)
print("Catégorie :", category)
print("Type :", song_type)
print("Langue :", language)
print("Auteur :", author_name)
if not confirm("Je vais enregistrer ces informations dans le kara, d'accord ?"):
print("Ok, j'arrête.")
sys.exit(0)
print("Enregistrement...")
if mkvpropedit(sys.argv[1], source_name, song_name, category, language, author_name, song_type):
print("Oups ! Quelque chose s'est mal passé on dirait...")
sys.exit(1)
print("C'est bon !")
def convert_home_kara(ROOT):
rgx = re.compile(r"(.*) - (ED|OP|AMV|IS|VOCA)\d* - (.*)")
categories = {
"vo" : "vo",
"va" : "va",
"vf" : "va",
"cdg" : "cdg",
"autres" : "autres",
"autre" : "autres",
"amv" : "amv",
"voca" : "vocaloid",
}
for (directory, _, children) in os.walk(ROOT):
for child in children:
child, ext = os.path.splitext(child)
if ext.lower() != ".mkv":
continue
try:
splited = directory.split("/")
_pseudo = splited[-2] if len(splited) >= 3 and splited[-3] in ["nouveau", "nouveaux"] else ""
_category = categories[splited[-1]]
[(_source, _type, _title)] = rgx.findall(child)
print(_category, _source, _type, _title)
filepath = os.path.join(directory, child) + ".mkv"
res = mkvpropedit(filepath, _source, _title, _category, "undefined", _pseudo, _type)
except (KeyError, ValueError):
continue
if __name__ == "__main__":
if not has_exe("mkvpropedit"):
print("Il te manque mkvpropedit, qui fait partie de mkvtoolnix.\nLes instructions d'installation sont disponibles ici :\nhttps://mkvtoolnix.download/downloads.html")
sys.exit(1)
if len(sys.argv) != 2:
print("Utilisation : {} KARA.MKV".format(sys.argv[0]))
sys.exit(1)
if sys.argv[1] == "--init":
convert_home_kara("/home/kara/")
else:
try:
interactive()
except (EOFError, KeyboardInterrupt):
print("\nBye bye~")
.ycm_extra_conf.py
CC = gcc
CC = gcc -g
CFLAGS = `pkg-config --cflags mpv` -Wall -Wextra -std=c11 -pthread -ldl -Iinc
LDFLAGS = `pkg-config --libs mpv` -pthread -ldl
......@@ -6,25 +6,24 @@ LDFLAGS = `pkg-config --libs mpv` -pthread -ldl
EXE = bin/player.out
CLIENT = bin/client.out
UPDATOR = bin/update.out
FILES = socket.c mpv.c thread_pool.c server.c database.c sqlite3.c db/utils.c db/update.c
SEARCHOR = bin/search.out
FILES = socket.c mpv.c thread_pool.c server.c database.c sqlite3.c mkv.c bufferfd.c utils.c
OBJ := $(FILES:%.c=obj/%.o)
SRC := $(FILES:%.c=src/%.c)
OBJ = $(FILES:%.c=obj/%.o)
SRC = $(FILES:%.c=src/%.c)
all: prepare $(EXE) $(CLIENT) $(UPDATOR)
all: prepare style $(EXE) $(CLIENT) $(UPDATOR) $(SEARCHOR)
prepare:
-@mkdir obj/db
-@mkdir obj/main
style: ../astyle.sh
../astyle.sh 'src/*.c'
../astyle.sh 'src/db/*.c'
../astyle.sh 'src/main/*.c'
../astyle.sh 'inc/*.h'
../astyle.sh 'inc/db/*.h'
obj/%.o: src/%.c
......@@ -32,22 +31,26 @@ obj/%.o: src/%.c
clean:
-@rm $(EXE)
-@rm $(CLIENT)
-@rm $(EXE) $(CLIENT) $(UPDATOR)
-@rm bin/*.out
-@rm obj/*.o
-@rm obj/main/*.o
$(EXE): $(OBJ) obj/main/server.o
$(CC) -o $@ $^ $(LDFLAGS)
$(EXE): $(OBJ) obj/serv_main.o
$(CLIENT): $(OBJ) obj/main/client.o
$(CC) -o $@ $^ $(LDFLAGS)
$(CLIENT): $(OBJ) obj/client_main.o
$(CC) -o $@ obj/client_main.o obj/socket.o $(LDFLAGS)
$(UPDATOR): $(OBJ) obj/main/update.o
$(CC) -o $@ $^ $(LDFLAGS)
$(UPDATOR): $(OBJ) obj/db/update_main.o
$(CC) -o $@ obj/db/update_main.o obj/db/update.o obj/db/utils.o obj/sqlite3.o $(LDFLAGS)
$(SEARCHOR): $(OBJ) obj/main/search.o
$(CC) -o $@ $^ $(LDFLAGS)
.PHONY: clean style prepare
/* Module to read file more efficiently.
*
* `bufferfd` reduces the number of calls to `read` by maintaining a buffer.
*
* Usage:
*
* ```c
* struct bufferfd file;
* file.fd = fd;
* file.len = 0;
* file.pos = 0;
*
* // ...use the buffer...
* ```
*
* Return value of the functions:
* - On success, the number of bytes consumed (except bufferfd_seek returns 0)
* - On error, a negative value
*
* Note: the user should not interfere with the members of `bufferfd` once
* initialized.
*/
#pragma once
#include <stdint.h>
#include <stdlib.h>
#define BUFFER_MAX 4096
struct bufferfd
{
int fd;
uint8_t buffer[BUFFER_MAX];
size_t len;
size_t pos;
};
/* Read `n` bytes and copy them to `res`. */
ssize_t bufferfd_bytes(struct bufferfd * bf, size_t n, uint8_t * res);
/* Read a byte and copy it to `res`.
* Equivalent to `bufferfd_bytes(bf, 1, res)`. */
ssize_t bufferfd_byte(struct bufferfd * bf, uint8_t * res);
/* Consume `n` bytes without returning them. */
ssize_t bufferfd_skip(struct bufferfd * bf, size_t n);
/* Move the file cursor to the absolute position `pos` (0 is the beginning of
* the file).
* Internally calls `lseek(bf->fd, pos, SEEK_SET)`. */
ssize_t bufferfd_seek(struct bufferfd * bf, size_t pos);
......@@ -2,5 +2,174 @@
#define __DATABASE__
#include <stdbool.h>
#include <sys/types.h>
/**
* @brief The type for the database.
*/
typedef struct database database_t;
/**
* @brief Construct the database connection.
* @param filepath The path to the database.
* @return The function returns a pointer to the created database
* connection if the construct operation is possible,
* `NULL` otherwise.
*/
database_t * database_construct(const char * filepath);
/**
* @brief Destruct the `database` object.
* @param database The database to destruct.
*/
void database_destruct(database_t * database);
/**
* @brief Add a kara to the database.
* @param database The database pointer to update with the kara.
* @param karapath The path to the mkv file of the kara
* @return The function returns `true` uppon successfull completion, `false` otherwise.
*/
bool database_update_kara(database_t * database, const char * karapath);
/**
* @brief Search a kara and returns its id. The result is limited in size.
* @param database The database to search through.
* @param rgx A simple regex, where the wildcard is `%` and the jocker is `_` (it's
* an SQLITE standard regex). The string must be null terminated.
* @param out_array The output array.
* @param in_out_length In input needs to be the length of the array, after the
* function is called the variable stores the number of found elements.
* @return The function returns `true` uppon successfull operation, even of the result
* is empty. If anything went wrong, returns `false`.
*/
bool database_search_kara(database_t * database, const char * rgx, int out_array[], size_t * in_out_length);
/**
* @brief Add a kara by its id to the queue.
* @param database The database where to add the kara.
* @param kara The id of the kara to add to the database.
* @return The function returns `true` uppon successfull operation, `false` otherwise.
*/
bool database_queue_add_kara(database_t * database, int kara);
/**
* @brief Add a playlist by its name to the queue.
* @param database The database where to add the playlist.
* @param plt The name of the playlist to add to the queue.
* @return The function returns `true` uppon successfull operation, `false` otherwise.
*/
bool database_queue_add_playlist(database_t * database, const char * plt);
/**
* @brief Remove a kara by its id to the queue.
* @param database The database where to remove the kara.
* @param kara The id of the kara to remove from the queue.
* @return The function returns `true` uppon successfull operation, `false` otherwise.
*/
bool database_queue_del_kara(database_t * database, int kara);
/**
* @brief Remove a kara by its position in the queue.
* @param database The database where to remove the kara.
* @param pos The position of the kara to remove from the queue.
* @return The function returns `true` uppon successfull operation, `false` otherwise.
*/
bool database_queue_del_pos(database_t * database, int pos);
/**
* @brief Make the next operation on the queue.
* @param database The database to read the queue from.
* @param karapath The path pf the next kara in the queue.
* @param maxlen The maximal length of the buffer `karapath`.
* @return The function returns `true` uppon successfull operation, `false` otherwise.
*/
bool database_queue_next(database_t * database, char * karapath, size_t maxlen);
/**
* @brief Make the previous operation on the queue.
* @param database The database to read the queue from.
* @param karapath The path pf the next kara in the queue.
* @param maxlen The maximal length of the buffer `karapath`.
* @return The function returns `true` uppon successfull operation, `false` otherwise.
*/
bool database_queue_prev(database_t * database, char * karapath, size_t maxlen);
/**
* @brief Create a playlist in the database.
* @param database The database where to create the playlist.
* @param name The name of the database to create.
* @param len The length of the playlist's name.
* @return The function returns `true` uppon successfull operation, `false` otherwise.
*/
bool database_playlist_create(database_t * database, char * name, size_t len);
/**
* @brief Remove a playlist from the database.
* @param database The database where to remove the playlist from.
* @param name The name of the database to remove.
* @param len The length of the playlist's name.
* @return The function returns `true` uppon successfull operation, `false` otherwise.
*/
bool database_playlist_destroy(database_t * database, char * name, size_t len);
/**
* @brief Add a kara by a regex to a playlist.
* @param db The database where the kara and the playlist live.
* @param plname The name of the playlist.
* @param pllen The length of the playlist's name.
* @param rgx The regex to select the kara(s).
* @return The function returns `true` uppon successfull operation, `false` otherwise.
*/
bool database_playlist_add_kara(database_t * db, char * plname, size_t pllen, char * rgx);
/**
* @brief Add a kara to a playlist.
* @param db The database where the kara and the playlist live.
* @param plname The name of the playlist.
* @param pllen The length of the playlist's name.
* @param id The d to select the kara.
* @return The function returns `true` uppon successfull operation, `false` otherwise.
*/
bool database_playlist_add_kara_id(database_t * db, char * plname, size_t pllen, int id);
/**
* @brief Delete a kara by a regex from a playlist.
* @param db The database where the kara and the playlist live.
* @param plname The name of the playlist.
* @param pllen The length of the playlist's name.
* @param rgx The regex to select the kara(s).
* @return The function returns `true` uppon successfull operation, `false` otherwise.
*/
bool database_playlist_del_kara(database_t * db, char * plname, size_t pllen, char * rgx);
/**
* @brief Delete a kara from a playlist.
* @param db The database where the kara and the playlist live.
* @param plname The name of the playlist.
* @param pllen The length of the playlist's name.
* @param id The id to select the kara.
* @return The function returns `true` uppon successfull operation, `false` otherwise.
*/
bool database_playlist_del_kara_id(database_t * db, char * plname, size_t pllen, int id);
#endif // __DATABASE__
#ifndef __DB_UPDATE__
#define __DB_UPDATE__
#include "sqlite3.h"
int lektor_db_update(struct sqlite3 * db, const char * directory);
#endif // __DB_UPDATE__
#ifndef __DB_UTILS__
#define __DB_UTILS__
#include "sqlite3.h"
void serror(sqlite3 * db, const char * msg);
#endif // __DB_UTILS__
/* Metadata parsing
*
* Karas are stored in matroska (mkv) files. Specs here:
* - EBML (like binary XML or something):
* https://github.com/cellar-wg/ebml-specification/blob/master/specification.markdown
* - Matroska EBML structure:
* https://www.matroska.org/technical/specs/index.html
* - Matroska tags:
* https://www.matroska.org/technical/specs/tagging/index.html
*
* Kara metadata is saved as followed:
*
* +--------------+--------------+-----------------+
* | Metadata | matroska tag | TargetTypeValue |
* +--------------+--------------+-----------------+
* | song_name | TITLE | 50 |
* | source_name | TITLE | 70 |
* | category | CONTENT_TYPE | 50 |
* | language | ADDRESS | 50 |
* | author_name | ARTIST | 50 |
* | song_type | GENRE | 50 |
* +--------------+--------------+-----------------+
*/
#pragma once
#define LEKTOR_TAG_MAX 256
struct kara_metadata
{
char song_name[LEKTOR_TAG_MAX];
char source_name[LEKTOR_TAG_MAX];
char category[LEKTOR_TAG_MAX];
char language[LEKTOR_TAG_MAX];
char author_name[LEKTOR_TAG_MAX];
char song_type[LEKTOR_TAG_MAX];
};
/* Reads the .mkv file at `filename` and stores its metadata in `dst`.
*
* Returns 0 on success and -1 on error. */
int kara_metadata_read(struct kara_metadata * dst, const char * filename);
......@@ -52,17 +52,18 @@ bool mpv_main_loop_detached(thread_pool_t * thread_pool);
/**
* @brief Set the pause state of mpv.
* @param set_pause Must be the state wanted for the pause property.
* @brief Toggle / cycle the pause state of mpv.
* @return The function returns `true` uppon sucessfull completion, `false` otherwise.
*/
void mpv_set_pause(bool * set_pause);
bool mpv_toggle_pause(void);
/**
* @brief Get the pause state of mpv.
* @param is_pause The current state of the pause property.
* @return The function returns `true` uppon sucessfull completion, `false` otherwise.
*/
void mpv_get_pause(bool * is_pause);
bool mpv_get_pause(bool * is_pause);
/**
......
/* Random things. */
#pragma once
#include <stdint.h>
#include <stdlib.h>
/* Read `bytes` as the big endian representation of a 32-bit unsigned integer.
* `n` is the number of bytes in `bytes`
* Returns 0 if n is 0.
* Restriction: n <= 4 */
uint32_t be_uint32_t(const uint8_t bytes[], size_t n);
/* Same as `be_uint32_t` but for 64-bit unsigned integers.
* Restriction: n <= 8 */
uint64_t be_uint64_t(const uint8_t bytes[], size_t n);
#define _POSIX_C_SOURCE 200809L
#include <string.h>
#include <unistd.h>
#include <bufferfd.h>
static ssize_t bufferfd_fill(struct bufferfd * bf)
{
if (bf->len <= bf->pos)
{
ssize_t n = read(bf->fd, bf->buffer, BUFFER_MAX);
if (n < 0)
{ return -1; }
bf->len = (size_t) n;
bf->pos = 0;
}
return bf->len - bf->pos;
}
ssize_t bufferfd_bytes(struct bufferfd * bf, size_t n, uint8_t * res)
{
// TODO repeat until n is reached
ssize_t r = bufferfd_fill(bf);
if (r < (ssize_t) n)
{ return -1; }
memcpy(res, bf->buffer + bf->pos, n);
bf->pos += n;
return 1;
}
ssize_t bufferfd_byte(struct bufferfd * bf, uint8_t * res)
{
return bufferfd_bytes(bf, 1, res);
}
ssize_t bufferfd_skip(struct bufferfd * bf, size_t n)
{
// TODO less dirty bomb
uint8_t b[4096];
return bufferfd_bytes(bf, n, b);
}
ssize_t bufferfd_seek(struct bufferfd * bf, size_t pos)
{
if (lseek(bf->fd, pos, SEEK_SET) < 0)
{ return -1; }
// The call to lseek invalidated bf->buffer.
bf->pos = 0;
bf->len = 0;
return 0;
}
#include "database.h"
#include "sqlite3.h"
#include "mkv.h"
#define _GNU_SOURCE
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stddef.h>
#include <dirent.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stddef.h>
#include <stdio.h>
#include <linux/limits.h>
struct database
{
const char * filepath;
sqlite3 * sqlite_ptr;
};
database_t * database_construct(const char * filepath)
{
struct stat st;
struct database * ret = NULL;
if (stat(filepath, &st) != 0)
{ goto error_1; }
ret = calloc(1, sizeof(struct database));
ret->filepath = filepath;
if (sqlite3_open(filepath, &ret->sqlite_ptr) != 0)
{ goto error_2; }
return ret;
error_2:
free(ret);
error_1:
return NULL;
}
void database_destruct(database_t * database)
{
sqlite3_close(database->sqlite_ptr);
free(database);
}
bool database_update_kara(database_t * database, const char * karapath)
{
static const char SQL_INSERT_KARA[] =
"INSERT INTO "
"kara (song_name, source_name, category, song_type, language, file_path, is_new, author_name, author_year)"
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
;
sqlite3_stmt * stmt = NULL;
struct stat attrs;
bool status = false;
int code = SQLITE_OK;
struct kara_metadata data;
time_t the_time = time(NULL);
struct tm * the_local_time = localtime(&the_time);
char year[10];
code = snprintf(year, 10, "%d", the_local_time->tm_year + 1900);
if (code < 0 || code >= 10)
{ return false; }
if (stat(karapath, &attrs) < 0)
{ return false; }
if (kara_metadata_read(&data, karapath) != 0)
{ return false; }
if (sqlite3_prepare_v2(database->sqlite_ptr, SQL_INSERT_KARA, -1, &stmt, 0) != SQLITE_OK)
{ goto error; }
if ((sqlite3_bind_text(stmt, 1, data.song_name, strlen(data.song_name), 0) != SQLITE_OK)
|| (sqlite3_bind_text(stmt, 2, data.source_name, strlen(data.source_name), 0) != SQLITE_OK)
|| (sqlite3_bind_text(stmt, 3, data.category, strlen(data.category), 0) != SQLITE_OK)
|| (sqlite3_bind_text(stmt, 4, data.song_type, strlen(data.song_type), 0) != SQLITE_OK)
|| (sqlite3_bind_text(stmt, 5, data.language, strlen(data.language), 0) != SQLITE_OK)
|| (sqlite3_bind_text(stmt, 6, karapath, strlen(karapath), 0) != SQLITE_OK)
|| (sqlite3_bind_text(stmt, 7, "0", 1, 0) != SQLITE_OK)
|| (sqlite3_bind_text(stmt, 8, data.author_name, strlen(data.author_name), 0) != SQLITE_OK)
|| (sqlite3_bind_text(stmt, 9, year, strlen(year), 0) != SQLITE_OK)
)
{ goto error; }
code = sqlite3_step(stmt);
if (code == SQLITE_BUSY)
{
sqlite3_exec(database->sqlite_ptr, "ROLLBACK;", 0, 0, 0);
goto error;
}
if (code != SQLITE_DONE)
{ goto error; }
status = true;
error:
sqlite3_finalize(stmt);
return status;
}
bool database_search_kara(database_t * database,
const char * rgx,
int out_array[],
size_t * in_out_length)
{
static const char * SQL_SEARCH_KARA = "SELECT id FROM view_kara WHERE string LIKE ?";
size_t len = strlen(rgx);
bool status = false;
int code = SQLITE_OK;
sqlite3_stmt * stmt = NULL;
if (sqlite3_prepare_v2(database->sqlite_ptr, SQL_SEARCH_KARA, -1, &stmt, 0) != SQLITE_OK)
{ goto error; }
if (sqlite3_bind_text(stmt, 1, rgx, len, 0) != SQLITE_OK)
{ goto error; }
len = 0;
while (true)
{
code = sqlite3_step(stmt);
if (code == SQLITE_BUSY)
{
sqlite3_exec(database->sqlite_ptr, "ROLLBACK;", 0, 0, 0);
goto error;
}
if (code == SQLITE_ROW)
{
if (len > *in_out_length)
{ break; }
else
{ out_array[len++] = sqlite3_column_int(stmt, 0); }
continue;
}
if (code == SQLITE_DONE)
{ break; }
goto error;
}
status = true;
*in_out_length = len;
error:
sqlite3_finalize(stmt);
return status;
}
bool database_queue_add_kara(database_t * database, int kara)
{
static const char * SQL_ADD_KARA =
"INSERT INTO queue (position, kara_id) "
"SELECT MAX(q.position) + 1, ? "
"FROM queue AS q"
;
bool status = false;
int code = SQLITE_OK;
sqlite3_stmt * stmt = NULL;
if (sqlite3_prepare_v2(database->sqlite_ptr, SQL_ADD_KARA, -1, &stmt, 0) != SQLITE_OK)
{ goto error; }
if (sqlite3_bind_int(stmt, 1, kara) != SQLITE_OK)
{ goto error; }
code = sqlite3_step(stmt);
if (code == SQLITE_BUSY)
{
sqlite3_exec(database->sqlite_ptr, "ROLLBACK;", 0, 0, 0);
goto error;
}
status = (code == SQLITE_DONE || code == SQLITE_OK || code == SQLITE_ROW);
error:
sqlite3_finalize(stmt);
return status;
}
bool database_queue_add_playlist(database_t * database, const char * plt)
{
static const char * SQL_ADD_PLT =
"INSERT INTO queue (kara_id) "
"SELECT kara_id "
"FROM playlist LEFT OUTER JOIN kara_playlist "
" ON (id = playlist_id) "
" WHERE name = ? "
" ORDER BY kara_id RANDOM() "
;
bool status = false;
int code = SQLITE_OK;
sqlite3_stmt * stmt = NULL;
if (sqlite3_prepare_v2(database->sqlite_ptr, SQL_ADD_PLT, -1, &stmt, 0) != SQLITE_OK)
{ goto error; }
if (sqlite3_bind_text(stmt, 1, plt, strlen(plt), 0) != SQLITE_OK)
{ goto error; }
code = sqlite3_step(stmt);
if (code == SQLITE_BUSY)
{
sqlite3_exec(database->sqlite_ptr, "ROLLBACK;", 0, 0, 0);
goto error;
}
status = code == SQLITE_DONE;
error:
sqlite3_finalize(stmt);
return status;
}
bool database_queue_del_kara(database_t * database, int kara)
{
static const char * SQL_DEL_KARA = "DELETE FROM queue WHERE kara_id = ?";
bool status = false;
int code = SQLITE_OK;
sqlite3_stmt * stmt = NULL;
if (sqlite3_prepare_v2(database->sqlite_ptr, SQL_DEL_KARA, -1, &stmt, 0) != SQLITE_OK)
{ goto error; }
if (sqlite3_bind_int(stmt, 1, kara) != SQLITE_OK)
{ goto error; }
code = sqlite3_step(stmt);
if (code == SQLITE_BUSY)
{
sqlite3_exec(database->sqlite_ptr, "ROLLBACK;", 0, 0, 0);
goto error;
}
status = (code == SQLITE_DONE || code == SQLITE_OK || code == SQLITE_ROW);
error:
sqlite3_finalize(stmt);
return status;
}
bool database_queue_del_pos(database_t * database, int pos)
{
static const char * SQL_DEL_POS = "DELETE FROM queue WHERE position = ?";
bool status = false;
int code = SQLITE_OK;
sqlite3_stmt * stmt = NULL;
if (sqlite3_prepare_v2(database->sqlite_ptr, SQL_DEL_POS, -1, &stmt, 0) != SQLITE_OK)
{ goto error; }
if (sqlite3_bind_int(stmt, 1, pos) != SQLITE_OK)
{ goto error; }
code = sqlite3_step(stmt);
if (code == SQLITE_BUSY)
{
sqlite3_exec(database->sqlite_ptr, "ROLLBACK;", 0, 0, 0);
goto error;
}
status = code == SQLITE_DONE;
error:
sqlite3_finalize(stmt);
return status;
}
bool database_queue_next(database_t * database, char * karapath, size_t maxlen)
{
static const char * SQL_NEXT =
"SELECT string, position "
"FROM view_kara LEFT OUTER JOIN queue "
" ON view_kara.id = queue.kara_id "
" JOIN queue_state "
" ON position > current "
" ORDER BY position ASC LIMIT 1 "
;
char SQL_UPDATE[PATH_MAX] = {0};
bool status = false;
int code = SQLITE_OK;
sqlite3_stmt * stmt = NULL;
if (sqlite3_prepare_v2(database->sqlite_ptr, SQL_NEXT, -1, &stmt, 0) != SQLITE_OK)
{ goto error; }
code = sqlite3_step(stmt);
if (code == SQLITE_BUSY)
{
sqlite3_exec(database->sqlite_ptr, "ROLLBACK;", 0, 0, 0);
goto error;
}
if (code == SQLITE_ROW)
{
snprintf(karapath, maxlen, "%s", (const char *) sqlite3_column_text(stmt, 0));
snprintf(SQL_UPDATE, PATH_MAX, "UPDATE queue_state SET current = %d;", sqlite3_column_int(stmt, 1));
}
else
{ goto error; }
sqlite3_finalize(stmt);
return status = sqlite3_exec(database->sqlite_ptr, SQL_UPDATE, NULL, NULL, NULL) == SQLITE_OK;
error:
sqlite3_finalize(stmt);
return status;
}
bool database_queue_prev(database_t * database, char * karapath, size_t maxlen)
{
static const char * SQL_PREV =
"SELECT string, position "
"FROM view_kara LEFT OUTER JOIN queue "
" ON view_kara.id = queue.kara_id "
" JOIN queue_state "
" ON position < current "
" ORDER BY position DESC LIMIT 1 "
;
char SQL_UPDATE[PATH_MAX] = {0};
bool status = false;
int code = SQLITE_OK;
sqlite3_stmt * stmt = NULL;
if (sqlite3_prepare_v2(database->sqlite_ptr, SQL_PREV, -1, &stmt, 0) != SQLITE_OK)
{ goto error; }
code = sqlite3_step(stmt);
if (code == SQLITE_BUSY)
{
sqlite3_exec(database->sqlite_ptr, "ROLLBACK;", 0, 0, 0);
goto error;
}
if (code == SQLITE_ROW)
{
snprintf(karapath, maxlen, "%s", (const char *) sqlite3_column_text(stmt, 0));
snprintf(SQL_UPDATE, PATH_MAX, "UPDATE queue_state SET current = %d;", sqlite3_column_int(stmt, 1));
}
else
{ goto error; }
sqlite3_finalize(stmt);
return status = sqlite3_exec(database->sqlite_ptr, SQL_UPDATE, NULL, NULL, NULL) == SQLITE_OK;
error:
sqlite3_finalize(stmt);
return status;
}
bool database_playlist_create(database_t * database, char * name, size_t len)
{
static const char * SQL_ADD = "INSERT INTO playlist (name) VALUES (?)";
bool status = false;
int code = SQLITE_OK;
sqlite3_stmt * stmt = NULL;
for (size_t i = 0; i < len; ++i)
{
if (name[i] == '-')
{ return false; }
}
if (sqlite3_prepare_v2(database->sqlite_ptr, SQL_ADD, -1, &stmt, 0) != SQLITE_OK)
{ goto error; }
if (sqlite3_bind_text(stmt, 1, name, len, 0) != SQLITE_OK)
{ goto error; }
code = sqlite3_step(stmt);
if (code != SQLITE_DONE && code != SQLITE_OK && code != SQLITE_ROW)
{
sqlite3_exec(database->sqlite_ptr, "ROLLBACK;", 0, 0, 0);
goto error;
}
status = true;
error:
sqlite3_finalize(stmt);
return status;
}
bool database_playlist_destroy(database_t * database, char * name, size_t len)
{
static const char * SQL_DEL = "DELETE FROM playlist WHERE name = ?";
bool status = false;
int code = SQLITE_OK;
sqlite3_stmt * stmt = NULL;
if (sqlite3_prepare_v2(database->sqlite_ptr, SQL_DEL, -1, &stmt, 0) != SQLITE_OK)
{ goto error; }
if (sqlite3_bind_text(stmt, 1, name, len, 0) != SQLITE_OK)
{ goto error; }
code = sqlite3_step(stmt);
if (code != SQLITE_DONE && code != SQLITE_OK && code != SQLITE_ROW)
{
sqlite3_exec(database->sqlite_ptr, "ROLLBACK;", 0, 0, 0);
goto error;
}
status = true;
error:
sqlite3_finalize(stmt);
return status;
}
bool database_playlist_add_kara(database_t * db, char * plname, size_t pllen, char * rgx)
{
static const char * SQL_ADD =
"INSERT INTO kara_playlist (kara_id, playlist_id) "
"SELECT view_kara.id, playlist.id "
"FROM view_kara, playlist "
"WHERE playlist.name = ? "
" AND string LIKE ? "
;
bool status = false;
int code = SQLITE_OK;
sqlite3_stmt * stmt = NULL;
if (sqlite3_prepare_v2(db->sqlite_ptr, SQL_ADD, -1, &stmt, 0) != SQLITE_OK)
{ goto error; }
if ((sqlite3_bind_text(stmt, 1, plname, pllen, 0) != SQLITE_OK) &&
(sqlite3_bind_text(stmt, 2, rgx, strlen(rgx), 0) != SQLITE_OK))
{ goto error; }
code = sqlite3_step(stmt);
if (code != SQLITE_DONE && code != SQLITE_OK && code != SQLITE_ROW)
{
sqlite3_exec(db->sqlite_ptr, "ROLLBACK;", 0, 0, 0);
goto error;
}
status = true;
error:
sqlite3_finalize(stmt);
return status;
}
bool database_playlist_del_kara(database_t * db, char * plname, size_t pllen, char * rgx)
{
static const char * SQL_DEL =
"DELETE FROM kara_playlist "
"WHERE playlist_id = (SELECT id FROM playlist GROUP BY id HAVING name = ?) "
" AND kara_id IN (SELECT id FROM view_kara WHERE string LIKE ?) "
;
bool status = false;
int code = SQLITE_OK;
sqlite3_stmt * stmt = NULL;
if (sqlite3_prepare_v2(db->sqlite_ptr, SQL_DEL, -1, &stmt, 0) != SQLITE_OK)
{ goto error; }
if ((sqlite3_bind_text(stmt, 1, plname, pllen, 0) != SQLITE_OK) &&
(sqlite3_bind_text(stmt, 2, rgx, strlen(rgx), 0) != SQLITE_OK))
{ goto error; }
code = sqlite3_step(stmt);
if (code != SQLITE_DONE && code != SQLITE_OK && code != SQLITE_ROW)
{
sqlite3_exec(db->sqlite_ptr, "ROLLBACK;", 0, 0, 0);
goto error;
}
status = true;
error:
sqlite3_finalize(stmt);
return status;
}
bool database_playlist_add_kara_id(database_t * db, char * plname, size_t pllen, int id)
{
static const char * SQL_ADD =
"INSERT INTO kara_playlist (playlist_id, kara_id) "
"SELECT id, ? "
"FROM playlist GROUP BY id HAVING name = ? LIMIT 1"
;
bool status = false;
int code = SQLITE_OK;
sqlite3_stmt * stmt = NULL;
if (sqlite3_prepare_v2(db->sqlite_ptr, SQL_ADD, -1, &stmt, 0) != SQLITE_OK)
{ goto error; }
if ((sqlite3_bind_text(stmt, 2, plname, pllen, 0) != SQLITE_OK) &&
(sqlite3_bind_int(stmt, 1, id) != SQLITE_OK))
{ goto error; }
code = sqlite3_step(stmt);
if (code != SQLITE_DONE && code != SQLITE_OK && code != SQLITE_ROW)
{
sqlite3_exec(db->sqlite_ptr, "ROLLBACK;", 0, 0, 0);
goto error;
}
status = true;
error:
sqlite3_finalize(stmt);
return status;
}
bool database_playlist_del_kara_id(database_t * db, char * plname, size_t pllen, int id)
{
static const char * SQL_DEL =
"INSERT INTO kara_playlist (playlist_id, kara_id) "
"SELECT id, ? "
"FROM playlist GROUP BY id HAVING name = ? LIMIT 1"
;
bool status = false;
int code = SQLITE_OK;
sqlite3_stmt * stmt = NULL;
if (sqlite3_prepare_v2(db->sqlite_ptr, SQL_DEL, -1, &stmt, 0) != SQLITE_OK)
{ goto error; }
if ((sqlite3_bind_text(stmt, 2, plname, pllen, 0) != SQLITE_OK) &&
(sqlite3_bind_int(stmt, 1, id) != SQLITE_OK))
{ goto error; }
code = sqlite3_step(stmt);
if (code != SQLITE_DONE && code != SQLITE_OK && code != SQLITE_ROW)
{
sqlite3_exec(db->sqlite_ptr, "ROLLBACK;", 0, 0, 0);
goto error;
}
status = true;
error:
sqlite3_finalize(stmt);
return status;
}
#include "db/update.h"
#include "sqlite3.h"
#define _GNU_SOURCE
#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <inttypes.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <strings.h>
#include "db/utils.h"
static const char SQL_LAST_UPDATE[] = "SELECT last_update FROM misc;";
static const char SQL_INSERT_OLD_KARA[] =
"INSERT INTO "
"kara (song_name, source_name, category, song_type, language, file_path, is_new) "
"VALUES (?, ?, ?, ?, 'undefined', ?, 0)";
static const char SQL_INSERT_NEW_KARA[] =
"INSERT INTO "
"kara (song_name, source_name, category, song_type, language, file_path,"
" is_new, author_name) "
"VALUES (?, ?, ?, ?, 'undefined', ?, 1, ?)";
static inline void * mallocf(size_t n)
{
void * res = malloc(n);
if (!res)
{ fprintf(stderr, "Failed to allocate %ld bytes.\n", n); }
return res;
}
static inline void * reallocf(void * ptr, size_t n)
{
void * res = realloc(ptr, n);
if (!res)
{ fprintf(stderr, "Failed to allocate %ld bytes.\n", n); }
return res;
}
static int directories_and_files(const struct dirent * entry)
{
return (entry->d_type == DT_REG || entry->d_type == DT_DIR) &&
entry->d_name[0] != '.';
}
static int update_legacy_file(sqlite3 * db, const char * filename, size_t prefix, time_t last_update)
{
struct stat attrs;
if (stat(filename, &attrs) < 0)
{ return -1; }
if (attrs.st_mtim.tv_sec < last_update)
{ return 0; }
const size_t filename_length = strlen(filename);
if (filename_length > 4 && strncmp(filename + (filename_length - 4), ".mkv", 4) != 0)
{ return -2; }
const char * pseudo = 0;
const char * category = 0;
const char * name = 0;
const char * type = 0;
const char * title = 0;
size_t pseudo_len = 0,
category_len = 0,
name_len = 0,
type_len = 0,
title_len = 0;
const char * file_path = filename + prefix;
const char * f = file_path;
category_len = strcspn(f, "/");
if (!strncmp(f, "nouveau", category_len))
{
// "Nouveau" kara, in nouveaux/{pseudo}/{category}/...
f += category_len + 1; // Skip "nouveaux/"
pseudo_len = strcspn(f, "/");
pseudo = f;
f += pseudo_len + 1; // Skip "{pseudo}/"
category_len = strcspn(f, "/");
}
category = f;
f += category_len + 1; // Skip "{category}/"
name_len = strcspn(f, "-");
name = f;
f += name_len + 1;
type_len = strcspn(f, "-");
type = f;
f += type_len + 1;
title_len = strcspn(f, ".");
title = f;
if (!title)
{
fprintf(stderr, "Bad file path: '%s'.\n", filename);
return -1;
}
int status_code = -1;
sqlite3_stmt * stmt = 0;
const char * sql = pseudo ? SQL_INSERT_NEW_KARA : SQL_INSERT_OLD_KARA;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, 0) != SQLITE_OK)
{
serror(db, "Failed to prepare statement");
goto error;
}
if (sqlite3_bind_text(stmt, 1, name, name_len, 0) != SQLITE_OK)
{
serror(db, "Failed to bind song_name");
goto error;
}
if (sqlite3_bind_text(stmt, 2, title, title_len, 0) != SQLITE_OK)
{
serror(db, "Failed to bind song_source");
goto error;
}
if (sqlite3_bind_text(stmt, 3, category, -1, 0) != SQLITE_OK)
{
serror(db, "Failed to bind category");
goto error;
}
if (sqlite3_bind_text(stmt, 4, type, -1, 0) != SQLITE_OK)
{
serror(db, "Failed to bind type");
goto error;
}
if (sqlite3_bind_text(stmt, 5, file_path, -1, 0) != SQLITE_OK)
{
serror(db, "Failed to bind file_path");
goto error;
}
if (pseudo && sqlite3_bind_text(stmt, 6, pseudo, -1, 0) != SQLITE_OK)
{
serror(db, "Failed to bind pseudo");
goto error;
}
status_code = sqlite3_step(stmt);
if (status_code != SQLITE_DONE)
{
serror(db, "Failed to execute insert statement");
if (status_code == SQLITE_BUSY && sqlite3_exec(db, "ROLLBACK;", 0, 0, 0) != SQLITE_OK)
{ serror(db, "Failed to rollback transaction, database is corrupted"); }
goto error;
}
status_code = 0;
error:
sqlite3_finalize(stmt);
fprintf(stderr, "error with (kara : <%s>, filepath : <%s>)\n", file_path, filename);
return status_code;
}
static int update_directory(sqlite3 * db, const char * root, time_t last_update)
{
int status_code = -1;
const size_t root_len = strlen(root) + 1;
char ** queue = 0;
size_t queue_max = 128;
size_t queue_len = 0;
queue = (char **) mallocf(queue_max * sizeof(char *));
if (!queue)
{ goto error; }
queue[0] = strdup(root);
queue_len++;
while (queue_len)
{
struct dirent ** children;
int n;
char * dir = queue[--queue_len];
n = scandir(dir, &children, directories_and_files, alphasort);
if (n < 0)
{ fprintf(stderr, "Failed to open '%s': %s.\n", dir, strerror(errno)); }
for (int i = 0; i < n; i++)
{
size_t dir_len = strlen(dir);
// child length (256) + null byte + slash
char * child = (char *) mallocf(dir_len + 258);
if (!child)
{ goto error; }
memcpy(child, dir, dir_len);
child[dir_len] = '/';
strcpy(child + dir_len + 1, children[i]->d_name);
if (children[i]->d_type == DT_REG)
{
if (update_legacy_file(db, child, root_len, last_update) < 0)
{ fprintf(stderr, "Failed to add '%s'.\n", child); }
free(child);
}
else if (queue_len == queue_max)
{
queue_max *= 2;
char ** new_queue = (char **) reallocf(queue, queue_max * sizeof(char *));
if (!new_queue)
{ goto error; }
queue = new_queue;
queue[queue_len++] = child;
}
else
{
queue[queue_len++] = child;
}
}
}
status_code = 0;
error:
for (size_t i = 0; i < queue_len; i++)
{ free(queue[i]); }
free(queue);
return status_code;
}
int lektor_db_update(sqlite3 * db, const char * directory)
{
if (sqlite3_exec(db, "BEGIN TRANSACTION;", 0, 0, 0) != SQLITE_OK)
{
serror(db, "Failed to start transaction");
return -1;
}
sqlite3_stmt * stmt = 0;
time_t last_update = 0;
if (sqlite3_prepare_v2(db, SQL_LAST_UPDATE, -1, &stmt, 0) != SQLITE_OK)
{
serror(db, "Failed to get last update time");
goto error;
}
switch (sqlite3_step(stmt))
{
case SQLITE_ROW:
last_update = (time_t) sqlite3_column_int(stmt, 0);
break;
case SQLITE_DONE:
fprintf(stderr, "Failed to get last update time: table misc is empty");
goto error;
default:
serror(db, "Failed to get last update time");
goto error;
}
if (update_directory(db, directory, last_update) < 0)
{ goto error; }
if (sqlite3_exec(db, "COMMIT;", 0, 0, 0) != SQLITE_OK)
{
serror(db, "Failed to commit transaction");
goto error;
}
sqlite3_finalize(stmt);
return 0;
error:
sqlite3_finalize(stmt);
if (sqlite3_exec(db, "ROLLBACK;", 0, 0, 0) != SQLITE_OK)
{ serror(db, "Failed to rollback transaction, database is corrupted"); }
return -1;
}
#include "db/update.h"
#include "sqlite3.h"
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
int main(int argc, char ** argv)
{
struct stat path_stat;
if (argc != 3)
{ return 1; }
char * arg1 = argv[1];
char * arg2 = argv[2];
size_t len1 = strlen(arg1);
size_t len2 = strlen(arg2);
/* Get the sqlite database */
if ((len1 - 5 > 0) && strncmp(arg1 + (len1 - 5), ".sqlt", 5) == 0)
{
stat(arg1, &path_stat);
if (! S_ISREG(path_stat.st_mode))
{ return 10; }
}
/* Get the root of the kara base, can't be `/` */
if (len2 < 2)
{ return 21; }
stat(arg2, &path_stat);
if (! S_ISDIR(path_stat.st_mode))
{ return 20; }
/* Open the sqlite database */
sqlite3 * db = NULL;
if ( sqlite3_open(arg1, &db) )
{ return 50; }
int sta = lektor_db_update(db, arg2);
if (sta != 0)
{ return 60; }
sqlite3_close(db);
return 0;
}
#include "db/utils.h"
#include "sqlite3.h"
#include <stdio.h>
void serror(sqlite3 * db, const char * msg)
{
fprintf(stderr, "%s: %s\n", msg, sqlite3_errmsg(db));
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stddef.h>
#include <unistd.h>
#include <inttypes.h>
#include <linux/limits.h>
#include <string.h>
#include <strings.h>
#include "socket.h"
int main(int argc, char * argv[])
{
(void)argc;
(void)argv;
if (argc < 2)
{ return -1; }
socket_t * sock = socket_construct(true);
int fd = socket_get_file_descriptor(sock);
uint16_t count = 0;
uint16_t len = 8;
uint16_t _0x0001 = 1;
int step = 0;
uint16_t len = 0;
char msg[PATH_MAX] = {0};
for (int i = 1; i < argc; ++i)
{
strncat(msg, argv[i], PATH_MAX - 1);
len = strlen(msg);
if (i != argc - 1)
{ strncat(msg, " ", PATH_MAX - 1); }
}
printf("(%d), %s\n", len, msg);
if (2 != write(fd, &_0x0001, sizeof(uint16_t)))
{ goto error; }
......@@ -28,7 +47,7 @@ int main(int argc, char * argv[])
step++;
if ( 4 != write(fd, "seek -10", len) )
if ( 4 != write(fd, argv[1], len) )
{ goto error; }
socket_destruct(sock);
......
0% Chargement en cours ou .
You are about to add 0 people to the discussion. Proceed with caution.
Veuillez vous inscrire ou vous pour commenter