config.c
/** \file
* Key/value parser until we have a proper config
*/
/*
* Copyright (C) 2009 Trammell Hudson <hudson+ml@osresearch.net>
*
* This program 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 2
* of the License, or (at your option) any later version.
*
* This program 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 this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "dryos.h"
#include "config.h"
#include "version.h"
#include "bmp.h"
#include "module.h"
#include "menu.h"
#include "property.h"
#include "beep.h"
#include "shoot.h"
#include "zebra.h"
static int config_selected = 0;
static char config_selected_by_key[9] = "";
static char config_selected_by_mode[9] = "";
static char config_selected_by_name[9] = "";
static int config_ok = 0;
static int config_deleted = 0;
static char* config_file_buf = 0;
static int config_file_size = 0;
static int config_file_pos = 0;
extern struct config_var _config_vars_start[];
extern struct config_var _config_vars_end[];
static struct semaphore *config_save_sem = 0;
static struct config *config_parse_line(const char *line)
{
int name_len = 0;
int value_len = 0;
static struct config _cfg;
struct config * cfg = &_cfg;
// Trim any leading whitespace
int i = 0;
while( line[i] && isspace( line[i] ) )
i++;
// Copy the name to the name buffer
while( line[i]
&& !isspace( line[i] )
&& line[i] != '='
&& name_len < MAX_NAME_LEN
)
cfg->name[ name_len++ ] = line[i++];
if( name_len == MAX_NAME_LEN )
goto parse_error;
// And nul terminate it
cfg->name[ name_len ] = '\0';
// Skip any white space and = signs
while( line[i] && isspace( line[i] ) )
i++;
if( line[i++] != '=' )
goto parse_error;
while( line[i] && isspace( line[i] ) )
i++;
// Copy the value to the value buffer
while( line[i] && value_len < MAX_VALUE_LEN )
cfg->value[ value_len++ ] = line[ i++ ];
// Back up to trim any white space
while( value_len > 0 && isspace( cfg->value[ value_len-1 ] ) )
value_len--;
// And nul terminate it
cfg->value[ value_len ] = '\0';
DebugMsg( DM_MAGIC, 3,
"%s: '%s' => '%s'",
__func__,
cfg->name,
cfg->value
);
return cfg;
parse_error:
DebugMsg( DM_MAGIC, 3,
"%s: PARSE ERROR: len=%d,%d string='%s'",
__func__,
name_len,
value_len,
line
);
bmp_printf(FONT_LARGE, 10, 150, "CONFIG PARSE ERROR");
bmp_printf(FONT_MED, 10, 200,
"%s: PARSE ERROR:\nlen=%d,%d\nstring='%s'",
__func__,
name_len,
value_len,
line
);
msleep(2000);
//~ FreeMemory( cfg );
call("dumpf");
//~ malloc_error:
return 0;
}
static int get_char_from_config_file(char* out)
{
if (config_file_pos >= config_file_size) return 0;
*out = config_file_buf[config_file_pos++];
return 1;
}
static int read_line(char *buf, size_t size)
{
size_t len = 0;
while( len < size )
{
int rc = get_char_from_config_file(buf+len);
if( rc <= 0 )
return -1;
if( buf[len] == '\r' )
continue;
if( buf[len] == '\n' )
{
buf[len] = '\0';
return len;
}
len++;
}
return -1;
}
static void config_auto_parse(struct config *cfg)
{
for(struct config_var *var = _config_vars_start; var < _config_vars_end ; var++ )
{
#if defined(POSITION_INDEPENDENT)
var->name = PIC_RESOLVE(var->name);
var->value = PIC_RESOLVE(var->value);
#endif
if( !streq( var->name, cfg->name ) )
continue;
DebugMsg( DM_MAGIC, 3, "%s: '%s' => '%s'", __func__, cfg->name, cfg->value);
*(int*) var->value = atoi( cfg->value );
return;
}
DebugMsg( DM_MAGIC, 3, "%s: '%s' unused?", __func__, cfg->name );
}
int config_save_file(const char *filename)
{
int count = 0;
DebugMsg( DM_MAGIC, 3, "%s: saving to %s", __func__, filename );
#define MAX_SIZE 10240
char* msg = fio_malloc(MAX_SIZE);
msg[0] = '\0';
snprintf( msg, MAX_SIZE,
"# Magic Lantern %s (%s)\n"
"# Built on %s by %s\n",
build_version,
build_id,
build_date,
build_user
);
struct tm now;
LoadCalendarFromRTC( &now );
snprintf(msg + strlen(msg), MAX_SIZE - strlen(msg),
"# Configuration saved on %04d/%02d/%02d %02d:%02d:%02d\n",
now.tm_year + 1900,
now.tm_mon + 1,
now.tm_mday,
now.tm_hour,
now.tm_min,
now.tm_sec
);
for(struct config_var *var = _config_vars_start; var < _config_vars_end ; var++ )
{
if (*(int*)var->value == var->default_value)
continue;
snprintf(msg + strlen(msg), MAX_SIZE - strlen(msg) - 1,
"%s = %d\r\n",
var->name,
*(int*) var->value
);
count++;
}
FILE * file = FIO_CreateFile( filename );
if(!file)
{
fio_free(msg);
return -1;
}
FIO_WriteFile(file, msg, strlen(msg));
FIO_CloseFile( file );
fio_free(msg);
return count;
}
static struct config *config_parse()
{
char line_buf[1000];
struct config * cfg = 0;
int count = 0;
while( read_line(line_buf, sizeof(line_buf) ) >= 0 )
{
//~ bmp_printf(FONT_SMALL, 0, 0, "cfg line: %s ", line_buf);
// Ignore any line that begins with # or is empty
if( line_buf[0] == '#' || line_buf[0] == '\0' )
continue;
DebugMsg(DM_MAGIC, 3, "cfg line: %s", line_buf);
struct config * new_config = config_parse_line( line_buf );
if( !new_config )
goto error;
cfg = new_config;
count++;
config_auto_parse( cfg );
}
DebugMsg( DM_MAGIC, 3, "%s: Read %d config values", __func__, count );
return cfg;
error:
DebugMsg( DM_MAGIC, 3, "%s: ERROR", __func__ );
return NULL;
}
int config_autosave = 1;
int config_flag_file_setting_load(char* file)
{
uint32_t size;
return ( FIO_GetFileSize( file, &size ) == 0 );
}
void config_flag_file_setting_save(char* file, int setting)
{
FIO_RemoveFile(file);
if (setting)
{
FILE* f = FIO_CreateFile(file);
if (f) FIO_CloseFile(f);
}
}
static MENU_SELECT_FUNC(config_autosave_toggle)
{
char autosave_flag_file[0x80];
snprintf(autosave_flag_file, sizeof(autosave_flag_file), "%sAUTOSAVE.NEG", get_config_dir());
config_flag_file_setting_save(autosave_flag_file, !!config_autosave);
msleep(50);
config_autosave = !config_flag_file_setting_load(autosave_flag_file);
}
int config_parse_file(const char *filename)
{
char autosave_flag_file[0x80];
snprintf(autosave_flag_file, sizeof(autosave_flag_file), "%sAUTOSAVE.NEG", get_config_dir());
config_autosave = !config_flag_file_setting_load(autosave_flag_file);
config_file_buf = (void*)read_entire_file(filename, &config_file_size);
config_file_pos = 0;
config_parse();
fio_free(config_file_buf);
config_file_buf = 0;
return 1;
}
static struct config_var* config_var_lookup(int* ptr)
{
for(struct config_var *var = _config_vars_start; var < _config_vars_end ; var++ )
{
if (var->value == ptr)
{
return var;
}
}
#ifdef CONFIG_MODULES
return module_config_var_lookup(ptr);
#else
return 0;
#endif
}
static struct config_var * get_config_var_struct(const char * name)
{
for(struct config_var * var = _config_vars_start ; var < _config_vars_end ; var++ )
{
if (streq(var->name, name))
{
return var;
}
}
#ifdef CONFIG_MODULES
return module_get_config_var(name);
#else
return 0;
#endif
}
int get_config_var(const char * name)
{
struct config_var * var = get_config_var_struct(name);
if(var && var->value)
{
return *(var->value);
}
return 0;
}
static int set_config_var_struct(struct config_var * var, int new_value)
{
if(var && var->value)
{
//check if the callback routine exists
if(var->update)
{
//run the callback routine
int cbr_result = var->update(var, *(var->value), new_value);
//if the cbr returns false, it means we are not allowed to change the value
if(cbr_result)
{
*(var->value) = new_value;
return cbr_result;
}
}
else
{
//no cbr so just set the value
*(var->value) = new_value;
return 1;
}
}
return 0;
}
int set_config_var(const char * name, int new_value)
{
return set_config_var_struct(get_config_var_struct(name), new_value);
}
int set_config_var_ptr(int* ptr, int new_value)
{
struct config_var * var = config_var_lookup(ptr);
if(ptr && !var)
{
//this is not actually a config var, so just set the value
*ptr = new_value;
return 1;
}
return set_config_var_struct(var, new_value);
}
int config_var_was_changed(int* ptr)
{
struct config_var * var = config_var_lookup(ptr);
if (!var) return 0;
return var->default_value != *(var->value);
}
int config_var_restore_default(int* ptr)
{
struct config_var * var = config_var_lookup(ptr);
if (!var) return 0;
*(var->value) = var->default_value;
return 1;
}
#ifdef CONFIG_MODULES
/** module config files */
static void
module_config_parse(module_entry_t * module) {
char line_buf[ 1000 ];
while( read_line(line_buf, sizeof(line_buf) ) >= 0 )
{
// Ignore any line that begins with # or is empty
if( line_buf[0] == '#'
|| line_buf[0] == '\0' )
continue;
struct config * new_config = config_parse_line( line_buf );
if( !new_config ) return;
/* check for all registered config variables from this module */
for (module_config_t * mconfig = module->config; mconfig && mconfig->name; mconfig++)
{
/* check for config variable with the same name */
if(streq(new_config->name, mconfig->ref->name))
*mconfig->ref->value = atoi(new_config->value);
}
}
}
unsigned int module_config_load(char *filename, module_entry_t *module)
{
if (!module->config)
return -1;
config_file_buf = (void*)read_entire_file(filename, &config_file_size);
if (!config_file_buf)
return -1;
config_file_pos = 0;
module_config_parse(module);
fio_free(config_file_buf);
config_file_buf = 0;
return 0;
}
unsigned int module_config_save(char *filename, module_entry_t *module)
{
if (!module->config)
return -1;
char* msg = fio_malloc(MAX_SIZE);
msg[0] = '\0';
snprintf( msg, MAX_SIZE,
"# Config file for module %s (%s)\n\n",
module->name, module->filename
);
int count = 0;
for (module_config_t * mconfig = module->config; mconfig && mconfig->name; mconfig++)
{
if (*(int*)mconfig->ref->value == mconfig->ref->default_value)
continue;
snprintf(msg + strlen(msg), MAX_SIZE - strlen(msg) - 1,
"%s = %d\r\n",
mconfig->ref->name,
*(int*) mconfig->ref->value
);
count++;
}
if (count == 0)
{
/* everything is default, just delete the config file */
FIO_RemoveFile(filename);
goto finish;
}
FILE * file = FIO_CreateFile( filename );
if (!file)
{
fio_free(msg);
return -1;
}
FIO_WriteFile(file, msg, strlen(msg));
FIO_CloseFile( file );
finish:
fio_free(msg);
return 0;
}
#endif
#ifdef CONFIG_CONFIG_FILE
static MENU_UPDATE_FUNC(delete_config_update)
{
if (config_deleted)
{
MENU_SET_RINFO("Restart");
MENU_SET_WARNING(MENU_WARN_NOT_WORKING, "Restart your camera to complete the process.");
}
MENU_SET_HELP("Only the current preset: %s", get_config_dir());
}
static MENU_UPDATE_FUNC(config_save_update)
{
if (config_deleted)
{
MENU_SET_RINFO("Undo");
}
MENU_SET_HELP("%s", get_config_dir());
}
static void delete_config( void * priv, int delta )
{
char* path = get_config_dir();
struct fio_file file;
struct fio_dirent * dirent = FIO_FindFirstEx( path, &file );
if( IS_ERROR(dirent) )
return;
do
{
if (file.mode & ATTR_DIRECTORY)
{
continue; // is a directory
}
char fn[0x80];
snprintf(fn, sizeof(fn), "%s%s", path, file.name);
FIO_RemoveFile(fn);
}
while( FIO_FindNextEx( dirent, &file ) == 0);
FIO_FindClose(dirent);
config_deleted = 1;
if (config_autosave)
{
/* at shutdown, config autosave may re-create the config files we just deleted */
/* => disable this feature in RAM only, until next reboot, without commiting it to card */
config_autosave = 0;
}
}
/* config presets */
static const char* config_preset_file =
"ML/SETTINGS/CURRENT.SET"; /* contains the name of current preset */
static int config_preset_index = 0; /* preset being used right now */
static int config_new_preset_index = 0; /* preset that will be used after restart */
static int config_preset_num = 3; /* total presets available */
static char* config_preset_choices[16] = { /* preset names (reusable as menu choices) */
"OFF",
"Startup mode",
"Startup key",
"Preset 1 ",
"Preset 2 ",
"Preset 3 ",
"Preset 4 ",
"Preset 5 ",
"Preset 6 ",
"Preset 7 ",
"Preset 8 ",
"Preset 9 ",
"Preset 10 ",
"Preset 11 ",
"Preset 12 ",
"Preset 13 ", /* space needed: 8.3 */
};
static char config_dir[0x80];
static char* config_preset_name;
char* get_config_dir()
{
return config_dir;
}
/* null if no preset */
char* get_config_preset_name()
{
return config_preset_name;
}
static struct menu_entry cfg_menus[];
static void config_preset_scan()
{
char* path = "ML/SETTINGS/";
struct fio_file file;
struct fio_dirent * dirent = FIO_FindFirstEx( path, &file );
if(!IS_ERROR(dirent))
{
do
{
if (file.mode & ATTR_DIRECTORY)
{
if (file.name[0] == '.')
continue;
/* special names for keys pressed at startup */
if (streq(file.name + strlen(file.name)-4, ".KEY"))
continue;
/* special names for mode-based config presets */
if (streq(file.name + strlen(file.name)-4, ".MOD"))
continue;
/* we have reserved statically 12 chars for each preset */
snprintf(
config_preset_choices[config_preset_num], 12,
"%s", file.name
);
config_preset_num++;
if (config_preset_num >= COUNT(config_preset_choices))
break;
}
}
while( FIO_FindNextEx( dirent, &file ) == 0);
FIO_FindClose(dirent);
}
/* update the Config Presets menu */
cfg_menus[0].children[0].max = config_preset_num - 1;
}
static MENU_SELECT_FUNC(config_save_select)
{
config_save();
}
static MENU_SELECT_FUNC(config_preset_toggle)
{
menu_numeric_toggle(&config_new_preset_index, delta, 0, config_preset_num);
if (!config_new_preset_index)
{
FIO_RemoveFile(config_preset_file);
}
else
{
FILE* f = FIO_CreateFile(config_preset_file);
if (f)
{
if (config_new_preset_index == 1)
my_fprintf(f, "Startup mode");
else if (config_new_preset_index == 2)
my_fprintf(f, "Startup key");
else
my_fprintf(f, "%s", config_preset_choices[config_new_preset_index]);
FIO_CloseFile(f);
}
}
}
static MENU_UPDATE_FUNC(config_preset_update)
{
int preset_changed = (config_new_preset_index != config_preset_index);
char* current_preset_name = get_config_preset_name();
MENU_SET_RINFO(current_preset_name);
if (config_new_preset_index == 1) /* startup shooting mode */
{
char current_mode_name[9];
snprintf(current_mode_name, sizeof(current_mode_name), "%s", (char*) get_shootmode_name(shooting_mode_custom));
if (streq(config_selected_by_mode, current_mode_name))
{
MENU_SET_HELP("Config preset is selected by startup mode (on the mode dial).");
}
else
{
MENU_SET_RINFO("%s->%s", current_preset_name, current_mode_name);
if (config_selected_by_mode[0])
{
MENU_SET_HELP("Camera was started in %s; restart to load the config for %s.", config_selected_by_mode, current_mode_name);
}
else
{
MENU_SET_HELP("Restart to load the config for %s mode.", current_mode_name);
}
}
}
else if (config_new_preset_index == 2) /* startup key */
{
MENU_SET_HELP("At startup, press&hold MENU/PLAY/"INFO_BTN_NAME" to select the cfg preset.");
}
else /* named preset */
{
if (preset_changed)
{
MENU_SET_HELP("The new config preset will be used after you restart your camera.");
MENU_SET_RINFO("Restart");
}
}
}
int handle_select_config_file_by_key_at_startup(struct event * event)
{
if (!config_selected)
{
char* key_name = 0;
switch (event->param)
{
case BGMT_MENU:
key_name = "MENU";
break;
case BGMT_INFO:
key_name = INFO_BTN_NAME;
break;
case BGMT_PLAY:
key_name = "PLAY";
break;
}
if (key_name)
{
/* we are not able to check the filesystem at this point */
snprintf(config_selected_by_key, sizeof(config_selected_by_key), "%s", key_name);
return 0;
}
}
return 1;
}
static char* config_choose_startup_preset()
{
int size = 0;
/* by default, work in ML/SETTINGS dir */
snprintf(config_dir, sizeof(config_dir), "ML/SETTINGS/");
/* check for a preset file selected in menu */
char* preset_name = (char*) read_entire_file(config_preset_file, &size);
if (preset_name)
{
if (streq(preset_name, "Startup mode"))
{
/* will handle later */
config_preset_index = config_new_preset_index = 1;
}
else if (streq(preset_name, "Startup key"))
{
/* will handle later */
config_preset_index = config_new_preset_index = 2;
}
else
{
snprintf(config_selected_by_name, sizeof(config_selected_by_name), preset_name);
char preset_dir[0x80];
snprintf(preset_dir, sizeof(preset_dir), "ML/SETTINGS/%s", preset_name);
if (!is_dir(preset_dir)) { FIO_CreateDirectory(preset_dir); }
if (is_dir(preset_dir))
{
snprintf(config_dir, sizeof(config_dir), "%s/", preset_dir);
}
}
fio_free(preset_name);
}
/* scan the preset files and populate the menu */
config_preset_scan();
/* special cases: key pressed at startup, or startup mode */
/* key pressed at startup */
if (config_preset_index == 2)
{
if (config_selected_by_key[0])
{
char preset_dir[0x80];
snprintf(preset_dir, sizeof(preset_dir), "ML/SETTINGS/%s.KEY", config_selected_by_key);
if (!is_dir(preset_dir)) { FIO_CreateDirectory(preset_dir); }
if (is_dir(preset_dir))
{
/* success */
snprintf(config_dir, sizeof(config_dir), "%s/", preset_dir);
return config_selected_by_key;
}
}
/* didn't work */
return 0;
}
else config_selected_by_key[0] = 0;
/* startup shooting mode (if selected in menu) */
if (config_preset_index == 1)
{
snprintf(config_selected_by_mode, sizeof(config_selected_by_mode), "%s", get_shootmode_name(shooting_mode_custom));
char preset_dir[0x80];
snprintf(preset_dir, sizeof(preset_dir), "ML/SETTINGS/%s.MOD", config_selected_by_mode);
if (!is_dir(preset_dir)) { FIO_CreateDirectory(preset_dir); }
if (is_dir(preset_dir))
{
/* success */
snprintf(config_dir, sizeof(config_dir), "%s/", preset_dir);
return config_selected_by_mode;
}
/* didn't work */
return 0;
}
/* lookup the current preset in menu */
for (int i = 0; i < config_preset_num; i++)
{
if (streq(config_preset_choices[i], config_selected_by_name))
{
config_preset_index = config_new_preset_index = i;
return config_selected_by_name;
}
}
/* using default config */
return 0;
}
static struct menu_entry cfg_menus[] = {
{
.name = "Config files",
.select = menu_open_submenu,
.update = config_preset_update,
.submenu_width = 710,
.help = "Config auto save, manual save, restore defaults...",
.children = (struct menu_entry[]) {
{
.name = "Config preset",
.priv = &config_new_preset_index,
.min = 0,
.max = 2,
.choices = (const char **) config_preset_choices,
.select = config_preset_toggle,
.update = config_preset_update,
.help = "Choose a configuration preset."
},
{
.name = "Config AutoSave",
.priv = &config_autosave,
.max = 1,
.select = config_autosave_toggle,
.help = "If enabled, ML settings are saved automatically at shutdown."
},
{
.name = "Save config now",
.select = config_save_select,
.update = config_save_update,
.help = "Save ML settings to current preset directory."
},
{
.name = "Restore ML defaults",
.select = delete_config,
.update = delete_config_update,
.help = "This restores ML default settings, by deleting all CFG files.",
},
MENU_EOL,
},
},
};
#endif
/* called at startup, after init_func's */
void config_load()
{
#ifdef CONFIG_CONFIG_FILE
config_selected = 1;
config_preset_name = config_choose_startup_preset();
if (config_preset_name)
{
NotifyBox(2000, "Config: %s", config_preset_name);
if (!DISPLAY_IS_ON) beep();
}
char config_file[0x80];
snprintf(config_file, sizeof(config_file), "%smagic.cfg", get_config_dir());
config_parse_file(config_file);
#endif
config_ok = 1;
}
// this can be called from more tasks (gui, prop handler, menu), so it needs to be thread safe
void config_save()
{
#ifdef CONFIG_CONFIG_FILE
take_semaphore(config_save_sem, 0);
update_disp_mode_bits_from_params();
char config_file[0x80];
snprintf(config_file, sizeof(config_file), "%smagic.cfg", get_config_dir());
config_save_file(config_file);
config_menu_save_flags();
#ifdef CONFIG_MODULES
module_save_configs();
module_exec_cbr(CBR_CONFIG_SAVE);
#endif
if (config_deleted) config_autosave = 1; /* this can be improved, because it's not doing a proper "undo" */
config_deleted = 0;
give_semaphore(config_save_sem);
#endif
}
void config_save_at_shutdown()
{
#ifdef CONFIG_CONFIG_FILE
static int config_saved = 0;
if (config_ok && config_autosave && !config_saved)
{
config_saved = 1;
config_save();
msleep(100);
}
#endif
}
static void config_menu_init()
{
#ifdef CONFIG_CONFIG_FILE
menu_add( "Prefs", cfg_menus, COUNT(cfg_menus) );
config_save_sem = create_named_semaphore("config_save_sem",1);
#endif
}
INIT_FUNC("config", config_menu_init);