/* This file is part of Idest. Copyright (C) 2009-2011 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 rc; char *value = scm_to_locale_string(SCM_CAR(SCM_CDR(SCM_CDR(cell)))); rc = frame_field_from_string(frame, n, 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