/* This file is part of Idest.
Copyright (C) 2009-2011, 2015 Sergey Poznyakoff
Idest is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
Idest is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Idest. If not, see . */
#include "idest.h"
#ifdef GUILE_VERSION_NUMBER
#include
#include
int no_init_files_option;
int guile_inited = 0;
int guile_debug = 1;
SCM_GLOBAL_VARIABLE_INIT(sym_idest_main, "idest-main", SCM_EOL);
SCM_GLOBAL_VARIABLE_INIT(sym_idest_readonly, "idest-readonly", SCM_BOOL_T);
SCM_GLOBAL_SYMBOL(idest_text, "text");
SCM_GLOBAL_SYMBOL(idest_descr, "descr");
SCM_GLOBAL_SYMBOL(idest_rawdata, "rawdata");
SCM_GLOBAL_SYMBOL(idest_error, "idest-error");
static SCM
eval_catch_body(void *list)
{
SCM pair = (SCM)list;
return scm_apply_0(SCM_CAR(pair), SCM_CDR(pair));
}
static SCM
eval_catch_handler(void *data, SCM tag, SCM throw_args)
{
scm_handle_by_message_noexit("idest", tag, throw_args);
longjmp(*(jmp_buf*)data, 1);
}
struct scheme_exec_data {
SCM (*handler) (void *data);
void *data;
};
static SCM
scheme_safe_exec_body(void *data)
{
struct scheme_exec_data *ed = data;
return ed->handler(ed->data);
}
static int
guile_safe_exec(SCM (*handler)(void *data), void *data, SCM *result)
{
jmp_buf jmp_env;
struct scheme_exec_data ed;
SCM res;
if (setjmp(jmp_env))
return 1;
ed.handler = handler;
ed.data = data;
res = scm_c_catch(SCM_BOOL_T,
scheme_safe_exec_body, (void*)&ed,
eval_catch_handler, &jmp_env,
NULL, NULL);
if (result)
*result = res;
return 0;
}
static SCM
load_handler(void *data)
{
char **argv = data;
scm_set_program_arguments(-1, argv, NULL);
scm_primitive_load(scm_from_locale_string(argv[0]));
return SCM_UNDEFINED;
}
static SCM
load_handler_path(void *data)
{
char **argv = data;
scm_set_program_arguments(-1, argv, NULL);
scm_primitive_load_path(scm_from_locale_string(argv[0]));
return SCM_UNDEFINED;
}
static void
guile_load(char **argv, int use_path)
{
if (guile_safe_exec(use_path ? load_handler_path : load_handler,
argv, NULL))
error(1, 0, "cannot load %s", argv[0]);
}
static void
idest_guile_error(const char *subr,
const char *frame_id,
SCM field_id,
int errcode)
{
if (field_id != SCM_BOOL_F)
scm_error(idest_error, subr,
"frame ~A, field ~A: ~A",
scm_list_3(scm_from_locale_string(frame_id),
field_id,
scm_from_locale_string(idest_strerror(errcode))),
scm_list_2(scm_from_int(errcode), field_id));
else
scm_error(idest_error, subr,
"frame ~A: ~A (~A)",
scm_list_2(scm_from_locale_string(frame_id),
scm_from_locale_string(idest_strerror(errcode))),
scm_list_2(scm_from_int(errcode),
SCM_BOOL_F));
}
static SCM
stringlist_to_scm(union id3_field *field, int genre)
{
unsigned i, nstrings = id3_field_getnstrings(field);
SCM head = SCM_EOL, tail = SCM_EOL;
for (i = 0; i < nstrings; i++) {
SCM cell;
id3_ucs4_t const *ucs4;
char *str;
ucs4 = id3_field_getstrings(field, i);
if (!ucs4)
continue;
if (genre)
ucs4 = id3_genre_name(ucs4);
str = idest_ucs4_cvt(ucs4);
cell = scm_cons(scm_from_locale_string(str),
SCM_EOL);
free(str);
if (head == SCM_EOL)
head = cell;
else
SCM_SETCDR(tail, cell);
tail = cell;
}
return scm_string_append(head);
}
static SCM
field_to_scm(union id3_field *field, int genre)
{
id3_ucs4_t const *ucs4;
char *str;
SCM ret = SCM_EOL;
if (!field)
return ret;
switch (id3_field_type(field)) {
case ID3_FIELD_TYPE_TEXTENCODING:
case ID3_FIELD_TYPE_INT8:
case ID3_FIELD_TYPE_INT16:
case ID3_FIELD_TYPE_INT24:
case ID3_FIELD_TYPE_INT32:
case ID3_FIELD_TYPE_INT32PLUS:
ret = scm_from_long(field->number.value);
break;
case ID3_FIELD_TYPE_LATIN1:
case ID3_FIELD_TYPE_LATIN1FULL:
/* FIXME */
ret = scm_from_locale_string((char*)id3_field_getlatin1(field));
break;
case ID3_FIELD_TYPE_LATIN1LIST:
/* FIXME */
break;
case ID3_FIELD_TYPE_STRING:
ucs4 = id3_field_getstring(field);;
if (ucs4) {
str = idest_ucs4_cvt(ucs4);
ret = scm_from_locale_string(str);
free(str);
} else
ret = SCM_BOOL_F;
break;
case ID3_FIELD_TYPE_STRINGFULL:
ucs4 = id3_field_getfullstring(field);
if (ucs4) {
str = idest_ucs4_cvt(ucs4);
ret = scm_from_locale_string(str);
free(str);
} else
ret = SCM_BOOL_F;
break;
case ID3_FIELD_TYPE_STRINGLIST:
ret = stringlist_to_scm(field, genre);
break;
case ID3_FIELD_TYPE_LANGUAGE:
case ID3_FIELD_TYPE_FRAMEID:
case ID3_FIELD_TYPE_DATE:
ret = scm_from_locale_string(field->immediate.value);
break;
case ID3_FIELD_TYPE_BINARYDATA:
/* FIXME */
ret = SCM_EOL;
}
return ret;
}
static SCM
frame_dump_to_scm(struct id3_frame *frame)
{
int i;
union id3_field *field;
SCM head = SCM_EOL, tail = SCM_EOL;
for (i = 0; field = id3_frame_field(frame, i); i++) {
SCM cell;
char *s = field_to_string(field, 0);
if (!s)
idest_guile_error("frame_dump_to_scm",
frame->id, scm_from_int(i),
IDEST_ERR_BADFIELD);
cell = scm_cons(scm_list_3(scm_from_int(i),
scm_from_int(field->type),
scm_from_locale_string(s)),
SCM_EOL);
if (head == SCM_EOL)
head = cell;
else
SCM_SETCDR(tail, cell);
tail = cell;
}
return scm_list_1(scm_cons(idest_rawdata, head));
}
static SCM
frame_to_scm(struct id3_frame *frame)
{
int rc;
int i;
const struct idest_frametab *ft = idest_frame_lookup(frame->id);
struct ed_item itm;
SCM head = SCM_EOL, tail = SCM_EOL;
if (!ft)
head = frame_dump_to_scm(frame);
else {
ed_item_zero(&itm);
itm.name = xstrdup(frame->id);
memcpy(itm.id, frame->id, 4);
rc = ft->decode(&itm, frame);
if (rc)
idest_guile_error("frame_to_scm",
frame->id, SCM_BOOL_F, rc);
head = scm_cons(scm_cons(idest_text,
scm_from_locale_string(itm.value)),
SCM_EOL);
tail = head;
for (i = 0; i < itm.qc; i++) {
SCM cell;
cell = scm_cons(
scm_cons(scm_string_to_symbol(scm_from_locale_string(ft->qv[i])),
scm_from_locale_string(itm.qv[i])),
SCM_EOL);
SCM_SETCDR(tail, cell);
tail = cell;
}
ed_item_free_content(&itm);
}
return scm_cons(scm_from_locale_string(frame->id),
scm_cons(
scm_cons(idest_descr,
scm_from_locale_string(frame->description)),
head));
}
static SCM
tag_to_scm(struct id3_tag *tag)
{
struct id3_frame *frame;
unsigned i;
SCM scm_first = SCM_EOL, scm_last;
for (i = 0; (frame = id3_tag_findframe(tag, NULL, i)); i++) {
SCM new;
SCM fcell = frame_to_scm(frame);
if (fcell == SCM_EOL)
continue;
new = scm_cons(fcell, SCM_EOL);
if (scm_first == SCM_EOL)
scm_first = new;
else
SCM_SETCDR(scm_last, new);
scm_last = new;
}
return scm_first;
}
static int
qvname_to_ind(const struct idest_frametab *ft, const char *name)
{
int i;
for (i = 0; i < ft->qc; i++)
if (strcmp(ft->qv[i], name) == 0)
return i;
return -1;
}
static void
ed_item_from_scm(struct ed_item *itm, SCM list)
{
const struct idest_frametab *ft = idest_frame_lookup(itm->id);
if (!ft)
idest_guile_error("ed_item_from_scm",
itm->id, SCM_BOOL_F,
IDEST_ERR_BADTYPE);
itm->qc = ft->qc;
itm->qv = xcalloc(itm->qc, sizeof(itm->qv[0]));
for (; !scm_is_null(list) && scm_is_pair(list); list = SCM_CDR(list)) {
SCM elt = SCM_CAR(list);
SCM key;
char *s;
int n;
if (!scm_is_pair(elt))
scm_misc_error(NULL,
"Wrong element type: ~S",
scm_list_1(elt));
key = SCM_CAR(elt);
if (key == idest_text || key == idest_descr)
continue;
s = scm_to_locale_string(scm_symbol_to_string(key));
n = qvname_to_ind(ft, s);
if (n == -1)
idest_guile_error("ed_item_from_scm",
itm->id, key,
IDEST_ERR_BADFIELD);
free(s);
itm->qv[n] = scm_to_locale_string(SCM_CDR(elt));
}
#if 0
/* FIXME: Provide defaults? */
for (i = 0; i < itm->qc; i++) {
if (!itm->qv[i])
itm->qv[i] = xstrdup("");
}
#endif
}
static int
set_frame_from_rawdata(struct id3_frame *frame, SCM list)
{
for (; !scm_is_null(list); list = SCM_CDR(list)) {
SCM cell = SCM_CAR(list);
int n = scm_to_int(SCM_CAR(cell));
int type = scm_to_int(SCM_CAR(SCM_CDR(cell)));
char *value = scm_to_locale_string(SCM_CAR(SCM_CDR(SCM_CDR(cell))));
int rc;
rc = frame_field_from_rawdata(frame, n, type, value);
free(value);
if (rc)
return rc;
}
return 0;
}
static int
scm_to_tag(SCM scm, struct id3_tag *tag)
{
int modified = 0;
for (; !scm_is_null(scm) && scm_is_pair(scm); scm = SCM_CDR(scm)) {
int rc;
int rawdata = 0;
struct id3_frametype const *frametype;
struct id3_frame *frame;
struct ed_item itm;
char *id;
SCM x, text;
SCM elt = SCM_CAR(scm);
if (!scm_is_pair(elt))
scm_misc_error(NULL,
"Wrong element type: ~S",
scm_list_1(elt));
x = SCM_CAR(elt);
if (!scm_is_string(x))
scm_misc_error(NULL,
"Wrong car type: ~S",
scm_list_1(elt));
id = scm_to_locale_string(x);
/* frametype = id3_frametype_lookup(id, strlen(id)); */
/* if (!frametype) */
/* idest_guile_error("guile-transform", */
/* id, SCM_BOOL_F, */
/* IDEST_ERR_BADTYPE); */
ed_item_zero(&itm);
memcpy(itm.id, id, sizeof(itm.id));
itm.name = id;
x = SCM_CDR(elt);
if (scm_is_string(x)) {
itm.value = scm_to_locale_string(x);
} else if (scm_is_pair(x) &&
(text = scm_assoc_ref(x, idest_text)) !=
SCM_BOOL_F) {
itm.value = scm_to_locale_string(text);
ed_item_from_scm(&itm, x);
} else if (scm_is_pair(x) &&
(text = scm_assoc_ref(x, idest_rawdata)) !=
SCM_BOOL_F)
rawdata = 1;
else
scm_misc_error(NULL,
"Wrong cdr type: ~S",
scm_list_1(elt));
frame = id3_frame_new(id);
if (id3_tag_attachframe(tag, frame))
/* FIXME: user scm_error */
error(1, 0, "cannot attach new frame");
if (rawdata)
rc = set_frame_from_rawdata(frame, text);
else
rc = set_frame_value(frame, &itm);
if (rc)
idest_guile_error("guile-transform",
frame->id, SCM_BOOL_F, rc);
modified |= 1;
free(id);
free(itm.value);
qv_free(itm.qc, itm.qv);
}
return modified;
}
SCM
guile_apply_main(const char *file, struct id3_tag *tag)
{
jmp_buf jmp_env;
SCM cell;
if (setjmp(jmp_env))
error(1, 0, "idest-main failed");
cell = scm_cons(SCM_VARIABLE_REF(sym_idest_main),
scm_list_2(scm_from_locale_string(file),
tag_to_scm(tag)));
return scm_c_catch(SCM_BOOL_T,
eval_catch_body, cell,
eval_catch_handler, &jmp_env,
NULL, NULL);
}
int
guile_transform(const char *file, struct id3_tag *tag)
{
SCM result;
if (!guile_inited)
return 0;
result = guile_apply_main(file, tag);
if (scm_is_pair(result)) {
/* Remove all existing tags */
id3_tag_clearframes(tag);
/* Replace them with the new ones */
return scm_to_tag(result, tag);
}
return 0;
}
int
guile_list(const char *file, struct id3_tag *tag)
{
if (!guile_inited)
return 0;
guile_apply_main(file, tag);
return 1;
}
static void
load_path_prepend(const char *dir)
{
SCM scm, path_scm;
SCM *pscm;
path_scm = SCM_VARIABLE_REF(scm_c_lookup("%load-path"));
scm = scm_from_locale_string(dir);
if (scm_member(scm, path_scm) != SCM_BOOL_F)
return;
pscm = SCM_VARIABLE_LOC(scm_c_lookup("%load-path"));
*pscm = scm_cons(scm, path_scm);
}
static void
load_path_append(const char *dir)
{
SCM scm, path_scm;
SCM *pscm;
path_scm = SCM_VARIABLE_REF(scm_c_lookup("%load-path"));
scm = scm_from_locale_string(dir);
if (scm_member(scm, path_scm) != SCM_BOOL_F)
return;
pscm = SCM_VARIABLE_LOC(scm_c_lookup("%load-path"));
*pscm = scm_append(scm_list_2(path_scm, scm_list_1(scm)));
}
static char *
make_file_name(const char *dir, const char *file_name)
{
char *ptr;
size_t len = strlen(dir);
if (len > 0 && dir[len - 1] == '/')
len--;
ptr = xmalloc(len + 1 + strlen(file_name) + 1);
memcpy(ptr, dir, len);
ptr[len++] = '/';
strcpy(ptr + len, file_name);
return ptr;
}
static gl_list_t user_path_list[2];
static void
path_item_dispose(const void *elt)
{
free((void*)elt);
}
void
guile_add_load_path(const char *arg, int li)
{
const char *p;
size_t n, len;
if (!user_path_list[li])
user_path_list[li] =
gl_list_create_empty(&gl_linked_list_implementation,
NULL,
NULL,
path_item_dispose,
true);
for (len = strlen(arg); len; arg = p) {
char *s;
p = str_split_col(arg, &len, &n);
s = xmalloc(n + 1);
memcpy(s, arg, n);
s[n] = 0;
((li == 0) ? gl_list_add_first : gl_list_add_last)
(user_path_list[li], s);
}
}
static void
flush_user_load_path(int li)
{
if (user_path_list[li]) {
gl_list_iterator_t itr;
const void *p;
itr = gl_list_iterator(user_path_list[li]);
while (gl_list_iterator_next(&itr, &p, NULL))
((li == 0) ? load_path_prepend : load_path_append)(p);
gl_list_iterator_free(&itr);
gl_list_free(user_path_list[li]);
user_path_list[li] = NULL;
}
}
static void
load_startup_file()
{
int i;
const char init_name[] = ".idest.scm";
char *argv[4];
argv[0] = xstrdup(init_name);
argv[1] = make_file_name(getenv("HOME"), init_name);
argv[2] = make_file_name(PKG_SITE, "idest.scm");
argv[3] = NULL;
for (i = 0; argv[i]; i++) {
if (access(argv[i], R_OK) == 0) {
guile_load(argv + i, 0);
break;
}
free(argv[i]);
}
/* Free the rest of arguments. */
for (; argv[i]; i++)
free(argv[i]);
}
SCM_DEFINE_PUBLIC(scm_sys_idest_package_site_dir, "%idest-package-site-dir",
0, 0, 0,
(),
"Return the directory where idest-specific version-independent files are installed.")
#define FUNC_NAME s_scm_sys_idest_package_site_dir
{
return scm_from_locale_string(PKG_SITE);
}
#undef FUNC_NAME
SCM_DEFINE_PUBLIC(scm_sys_idest_version_site_dir, "%idest-version-site-dir",
0, 0, 0,
(),
"Return the directory where version-specific site-wide files are installed.")
#define FUNC_NAME s_scm_sys_idest_version_site_dir
{
return scm_from_locale_string(VERSION_SITE);
}
#undef FUNC_NAME
SCM_DEFINE_PUBLIC(scm_sys_idest_guile_site_dir, "%idest-guile-site-dir",
0, 0, 0,
(),
"Return the Guile site-dir used when configuring the package.")
#define FUNC_NAME s_scm_sys_idest_guile_site_dir
{
return scm_from_locale_string(GUILE_SITE);
}
#undef FUNC_NAME
void
guile_init(int *pargc, char ***pargv)
{
SCM readonly, proc, args;
int argc, i;
char **argv;
if (!guile_argv)
return;
scm_init_guile();
scm_load_goops();
#include "guile.x"
if (guile_debug) {
#ifdef GUILE_DEBUG_MACROS
SCM_DEVAL_P = 1;
SCM_BACKTRACE_P = 1;
SCM_RECORD_POSITIONS_P = 1;
SCM_RESET_DEBUG_MODE;
#endif
}
scm_c_export("idest-main", "idest-readonly", NULL);
/* Set up load path */
flush_user_load_path(1);
load_path_prepend(GUILE_SITE);
load_path_prepend(PKG_SITE);
load_path_prepend(".");
load_path_prepend(VERSION_SITE);
flush_user_load_path(0);
if (!no_init_files_option)
load_startup_file();
guile_load(guile_argv, !strchr(guile_argv[0], '/'));
/* Read command line arguments */
args = scm_program_arguments();
argc = scm_to_int(scm_length(args));
/* Note: argv[0] is the script name. We will skip it. */
argv = xcalloc(argc, sizeof(argv[0]));
argc--;
for (i = 0, args = SCM_CDR(args); i < argc;
i++, args = SCM_CDR(args)) {
SCM sarg = SCM_CAR(args);
argv[i] = scm_to_locale_string(sarg);
}
argv[i] = NULL;
*pargc = argc;
*pargv = argv;
proc = SCM_VARIABLE_REF(sym_idest_main);
if (proc == SCM_EOL)
error(1, 0, "idest-main not defined");
if (scm_procedure_p(proc) != SCM_BOOL_T)
error(1, 0,
"idest-main is not a procedure object");
readonly = SCM_VARIABLE_REF(sym_idest_readonly);
if (readonly == SCM_BOOL_F)
mode = MODE_MOD;
else if (readonly == SCM_BOOL_T)
mode = MODE_QUERY;
else
error(1, 0,
"script %s set non-boolean value of idest-readonly",
guile_argv[0]);
guile_inited = 1;
}
#else
int
guile_transform(const char *file, struct id3_tag *tag)
{
return 0;
}
int
guile_list(const char *file, struct id3_tag *tag)
{
return 0;
}
void
guile_init(int *pargc, char ***pargv)
{
}
void
guile_add_load_path(const char *arg, int li)
{
}
#endif