Configuration loading, saving, scenario installation

pull/96/head
parent cb5027f73b
commit 76798776b0
No known key found for this signature in database
GPG Key ID: FF1D842BF4DDE92B

@ -53,11 +53,26 @@ void on_configuration_mode_switch(GtkWidget *self,main_window *widgets){
gtk_widget_show(window->Window);
gtk_main();
yon_ubl_status_box_spawn_infinite(GTK_CONTAINER(widgets->StatusBox),"config_mode",CONFIGURATION_MODE_STATUS_LABEL,BACKGROUND_IMAGE_INFO_TYPE);
yon_ubl_status_box_spawn_infinite(GTK_CONTAINER(widgets->StatusBox2),"config_mede",CONFIG_PATH_LABEL(main_config.config_save_path),BACKGROUND_IMAGE_INFO_TYPE);
GList *revealerlist = gtk_container_get_children(GTK_CONTAINER(widgets->StatusBox2));
GList *list = gtk_container_get_children(GTK_CONTAINER(g_list_nth_data(revealerlist,0)));
GtkWidget *box = GTK_WIDGET(list->data);
g_list_free(revealerlist);
g_list_free(list);
gtk_style_context_remove_class(gtk_widget_get_style_context(box),"boxInfoMessOK");
gtk_style_context_add_class(gtk_widget_get_style_context(box),"boxInfoMessGray");
main_config.configure_mode = 1;
gtk_widget_destroy(window->Window);
return;
} else {
yon_ubl_status_box_despawn_infinite(GTK_CONTAINER(widgets->StatusBox));
GList *revealerlist = gtk_container_get_children(GTK_CONTAINER(widgets->StatusBox2));
gtk_widget_destroy(GTK_WIDGET(g_list_nth_data(revealerlist,0)));
g_list_free(revealerlist);
main_config.configure_mode = 0;
}

@ -4,8 +4,9 @@ int yon_installation_start(main_window *widgets){
return !pthread_create(&main_config.install_thread,NULL,on_config_save,widgets);
}
void yon_quick_install(GtkWidget *, main_window *widgets){
void yon_quick_install(GtkWidget *self, main_window *widgets){
main_config.save_configured=1;
gtk_widget_hide(self);
pthread_create(&main_config.install_thread,NULL,on_config_save,widgets);
gtk_notebook_set_current_page(GTK_NOTEBOOK(widgets->Notebook),YON_PAGE_INSTALLATION);
}

@ -77,8 +77,19 @@ void yon_navigation_buttons_set_sensetiveness(main_window *widgets){
switch(page){
case YON_PAGE_WELCOME:
yon_load_proceed(YON_CONFIG_LOCAL);
gtk_widget_hide(widgets->BackButton);
gtk_widget_set_sensitive(widgets->NextButton,1);
gtk_widget_set_sensitive(widgets->CancelInstallButton,0);
gtk_widget_hide(widgets->BackButton);
gtk_widget_hide(widgets->SourceButton);
gtk_widget_hide(widgets->SkipInstallationButton);
gtk_widget_show(widgets->NextButton);
if (!yon_char_is_empty(config(AUTOINSTALL_TYPE_INSTALL))){
gtk_widget_show(widgets->StartScenarioButton);
} else {
gtk_widget_hide(widgets->StartScenarioButton);
}
break;
case YON_PAGE_LICENCE:
gtk_widget_show(widgets->BackButton);
@ -93,10 +104,14 @@ void yon_navigation_buttons_set_sensetiveness(main_window *widgets){
case YON_PAGE_KERNEL:
gtk_widget_set_sensitive(widgets->BackButton,0);
break;
case YON_PAGE_CONFIGURE_END:
gtk_button_set_label(GTK_BUTTON(widgets->NextButton),SAVE_AND_EXIT_LABEL);
gtk_button_set_label(GTK_BUTTON(widgets->CancelInstallButton),EXIT_LABEL);
break;
case YON_PAGE_COMPLETED:
case YON_PAGE_INSTALL_ERROR:
case YON_PAGE_CONFIGURE_SAVE:
case YON_PAGE_CONFIGURE_END:
case YON_PAGE_COMPLETION:
gtk_button_set_label(GTK_BUTTON(widgets->NextButton),RESTART_LABEL);
gtk_widget_set_sensitive(widgets->BackButton,0);
@ -171,6 +186,8 @@ int yon_page_save(main_window *widgets, enum YON_PAGES page){
case YON_PAGE_INSTALLATION_BEGIN:
return yon_installation_start(widgets);
break;
case YON_PAGE_CONFIGURE_END:
return yon_config_save(widgets);
default:return 1;
}
return 1;
@ -336,6 +353,9 @@ void yon_page_init(main_window *widgets, enum YON_PAGES page){
case YON_PAGE_INSTALLATION_BEGIN:
yon_install_init(widgets,page);
break;
case YON_PAGE_COMPLETION:
yon_config_restore(widgets);
break;
case YON_PAGE_INSTALLATION:
main_config.save_configured=1;
g_mutex_lock(&main_config.install_mutex);
@ -380,5 +400,7 @@ void on_page_cancel_clicked(GtkWidget *, main_window *widgets){
gtk_widget_set_sensitive(widgets->CancelInstallButton,0);
gtk_widget_set_sensitive(widgets->BackButton,1);
gtk_widget_set_sensitive(widgets->NextButton,1);
gtk_widget_set_sensitive(widgets->ConfigurationModeMenuItem,1);
yon_page_update(widgets);
}

@ -21,13 +21,12 @@ void yon_config_save_proceed(char *path, YON_CONFIG_TYPE type){
void yon_load_proceed(YON_CONFIG_TYPE type){
if (type!=YON_CONFIG_CUSTOM){
yon_config_clean();
if (!yon_char_is_empty(config_get_default_command))
yon_config_load_config(YON_CONFIG_DEFAULT,config_get_default_command,NULL);
yon_config_load_config(YON_CONFIG_DEFAULT,yon_config_get_command(main_config.config_load_path),NULL);
}
if (type==YON_CONFIG_GLOBAL){
yon_config_load_config(type,yon_config_get_command("global"),NULL);
yon_config_load_config(type,yon_config_get_command(main_config.config_load_path),NULL);
} else if (type==YON_CONFIG_LOCAL){
yon_config_load_config(type,yon_config_get_command("system"),NULL);
yon_config_load_config(type,yon_config_get_command(main_config.config_load_path),NULL);
} else if (type==YON_CONFIG_CUSTOM){
textdomain(template_ui_LocaleName);
GtkWidget *dialog = gtk_file_chooser_dialog_new(template_app_information.app_title,NULL,GTK_FILE_CHOOSER_ACTION_SAVE,CANCEL_LABEL,GTK_RESPONSE_CANCEL,OPEN_LABEL,GTK_RESPONSE_ACCEPT,NULL);
@ -59,12 +58,16 @@ void yon_load_proceed(YON_CONFIG_TYPE type){
void on_config_local_load(GtkWidget *,main_window *widgets){
if (!yon_char_is_empty(main_config.config_load_path)) free(main_config.config_load_path);
main_config.config_load_path = yon_char_new("system");
yon_load_proceed(YON_CONFIG_LOCAL);
yon_page_init(widgets,gtk_notebook_get_current_page(GTK_NOTEBOOK(widgets->Notebook)));
main_config.load_mode=YON_CONFIG_LOCAL;
}
void on_config_global_load(GtkWidget *,main_window *widgets){
if (!yon_char_is_empty(main_config.config_load_path)) free(main_config.config_load_path);
main_config.config_load_path = yon_char_new("global");
yon_load_proceed(YON_CONFIG_GLOBAL);
yon_page_init(widgets,gtk_notebook_get_current_page(GTK_NOTEBOOK(widgets->Notebook)));
main_config.load_mode=YON_CONFIG_GLOBAL;
@ -462,6 +465,7 @@ void on_config_global_save(GtkWidget *,main_window *widgets){
gboolean on_install_success(main_window *widgets){
gtk_label_set_text(GTK_LABEL(widgets->InstallationLabel),"");
gtk_notebook_set_current_page(GTK_NOTEBOOK(widgets->Notebook),YON_PAGE_COMPLETION);
yon_config_restore(widgets);
yon_page_update(widgets);
return 0;
@ -491,23 +495,26 @@ enum INSTALL_TYPE{
*
*/
enum INSTALL_TYPE yon_ubl_get_install_mode(){
char *value = config(AUTOINSTALL_TYPE_INSTALL);
if (!strcmp(value,"fast")){
return INSTALL_COMMON;
} else if (!strcmp(value,"part")){
return INSTALL_PART;
} else if (!strcmp(value,"next")){
return INSTALL_NEXT;
} else if (!strcmp(value,"advanced")){
return INSTALL_ADVANCED;
} else if (!strcmp(value,"grub_install")){
return INSTALL_GRUB_INSTALL;
} else if (!strcmp(value,"grub_update")){
return INSTALL_GRUB_UPDATE;
} else if (!strcmp(value,"system_only")){
return INSTALL_SYSTEM_ONLY;
} else if (!strcmp(value,"data_only")){
return INSTALL_USER_ONLY;
if (!yon_char_is_empty(value)){
if (!strcmp(value,"fast")){
return INSTALL_COMMON;
} else if (!strcmp(value,"part")){
return INSTALL_PART;
} else if (!strcmp(value,"next")){
return INSTALL_NEXT;
} else if (!strcmp(value,"advanced")){
return INSTALL_ADVANCED;
} else if (!strcmp(value,"grub_install")){
return INSTALL_GRUB_INSTALL;
} else if (!strcmp(value,"grub_update")){
return INSTALL_GRUB_UPDATE;
} else if (!strcmp(value,"system_only")){
return INSTALL_SYSTEM_ONLY;
} else if (!strcmp(value,"data_only")){
return INSTALL_USER_ONLY;
}
}
return INSTALL_ERROR;
}
@ -667,3 +674,131 @@ void *on_setup_system_configuration(void * data){
g_idle_add((GSourceFunc)on_install_success,widgets);
return NULL;
}
int yon_config_save(main_window *widgets){
{
int size=0;
config_str parameters = NULL;
enum INSTALL_TYPE install_mode = yon_ubl_get_install_mode();
switch(install_mode){
case INSTALL_COMMON:
parameters = yon_config_get_selection_by_key_no_ignored(&size,install_common_parameters,modules_parameter,NULL);
break;
case INSTALL_PART:
parameters = yon_config_get_selection_by_key_no_ignored(&size,install_part_parameters,modules_parameter,NULL);
break;
case INSTALL_NEXT:
parameters = yon_config_get_selection_by_key_no_ignored(&size,install_next_parameters,modules_parameter,NULL);
break;
case INSTALL_ADVANCED:
parameters = yon_config_get_selection_by_key_no_ignored(&size,install_advanced_parameters,modules_parameter,NULL);
break;
case INSTALL_GRUB_INSTALL:
case INSTALL_GRUB_UPDATE:
parameters = yon_config_get_selection_by_key_no_ignored(&size,install_grub_install_update_parameters,NULL);
break;
case INSTALL_SYSTEM_ONLY:
parameters = yon_config_get_selection_by_key_no_ignored(&size,install_system_only_parameters,NULL);
break;
case INSTALL_USER_ONLY:
parameters = yon_config_get_selection_by_key_no_ignored(&size,install_userdata_only_parameters,NULL);
break;
default:
yon_ubl_status_box_spawn(GTK_CONTAINER(widgets->StatusBox),ERROR_LABEL,5,BACKGROUND_IMAGE_FAIL_TYPE);
return 0;
}
char *command = yon_debug_output("%s\n",ubconfig_set_command_full(main_config.config_save_path,"",yon_char_parsed_to_string(parameters,size," ")));
if (system(command)){
return 0;
}
}
int size;
config_str all_parameters = yon_config_get_selection_by_key(&size,
root_password_parameter,
autologin_parameter,
xkbmodel_parameter,
xkblayout_parameter,
xkbvariant_parameter,
xkboptions_parameter,
hostname_parameter,
zone_parameter,
lang_parameter,
locale_parameter,
SERVICES_ENABLE_parameter,
GRUB_DEFAULT_parameter,
GRUB_TIMEOUT_parameter,
AUTOLOGINUSER_parameter,
GRUB_SUPERUSERS_parameter,
DOMAIN_parameter,
DOMAIN_admanger_parameter,
NTPSERVERS_parameter,
modules_extra_parameter,
KERNEL_BOOT_parameter,
packages_parameter,
NULL);
int user_size=0;
config_str users = yon_config_get_all_by_key(USERADD_parameter_search,&user_size);
if (user_size){
int final_size;
config_str final = yon_char_parsed_merge(all_parameters,size,users,user_size,&final_size);
yon_char_parsed_free(users,user_size);
if (size) yon_char_parsed_free(all_parameters,size);
all_parameters = final;
size = final_size;
}
users = yon_config_get_all_by_key(GRUB_PASSWORD_parameter_search,&user_size);
if (users){
int final_size;
config_str final = yon_char_parsed_merge(all_parameters,size,users,user_size,&final_size);
yon_char_parsed_free(users,user_size);
if (size) yon_char_parsed_free(all_parameters,size);
all_parameters = final;
size = final_size;
}
int network_size;
config_str networks = yon_config_get_all_by_key(NETWORK_parameter_search,&network_size);
if (network_size){
int final_size;
config_str final = yon_char_parsed_merge(all_parameters,size,networks,network_size,&final_size);
yon_char_parsed_free(networks,network_size);
if (size) yon_char_parsed_free(all_parameters,size);
all_parameters = final;
size = final_size;
}
if (all_parameters){
for (int i=0;i<size;i++){
char *parameter = yon_char_divide_search(all_parameters[i],"=",-1);
if (!strstr(parameter,"'")){
char *key = yon_config_parameter_get_key(parameter);
free(parameter);
char *tmp = yon_char_unite("'",key,"'",NULL);
free(key);
parameter = AUTOINSTALL(tmp);
free(tmp);
}
if (!strstr(all_parameters[i],"'")){
char *temp = yon_char_unite("'",all_parameters[i],"'",NULL);
free(all_parameters[i]);
all_parameters[i]=temp;
}
char *temp = yon_char_unite(parameter,"=",all_parameters[i],NULL);
free(all_parameters[i]);
all_parameters[i]=temp;
}
char *parameter_string = yon_char_parsed_to_string(all_parameters,size," ");
char *command = ubconfig_set_command_full(main_config.config_save_path,"",parameter_string);
if (!system(command)){
return 1;
}
}
return 0;
}
void yon_config_restore(main_window *widgets){
on_config_global_load(NULL,widgets);
system("ubconfig --source system remove [autoinstall] /");
yon_config_save_simple(YON_CONFIG_LOCAL,"system");
}

@ -284,6 +284,7 @@ main_window *yon_main_window_complete(){
widgets->MainWindow=yon_gtk_builder_get_widget(builder,"MainWindow");
widgets->StatusBox = yon_gtk_builder_get_widget(builder,"StatusBox");
widgets->StatusBox2 = yon_gtk_builder_get_widget(builder,"StatusBox2");
widgets->Notebook = yon_gtk_builder_get_widget(builder,"Notebook");
widgets->MainSpinner=yon_gtk_builder_get_widget(builder,"MainSpinner");
@ -686,8 +687,14 @@ main_window *yon_main_window_complete(){
g_object_unref(pix);
}
gtk_builder_connect_signals(builder,NULL);
yon_load_proceed(YON_CONFIG_LOCAL);
// yon_load_proceed(YON_CONFIG_LOCAL);
on_config_global_load(NULL,widgets);
// yon_interface_update(widgets);
if (!yon_char_is_empty(config(AUTOINSTALL_TYPE_INSTALL))){
gtk_widget_show(widgets->StartScenarioButton);
} else {
gtk_widget_hide(widgets->StartScenarioButton);
}
return widgets;
}

@ -422,6 +422,7 @@ typedef struct {
GtkWidget *MainSpinner;
GtkWidget *StatusBox;
GtkWidget *StatusBox2;
GtkWidget *WelcomeToggle;
GtkWidget *LicenceToggle;
GtkWidget *LocationToggle;
@ -1065,4 +1066,6 @@ void yon_quick_install(GtkWidget *, main_window *widgets);
void on_keyboard_layout_chosen(GtkCellRenderer *self, gchar *path, main_window *widgets);
void configuration_mode_accept(GtkWidget *,configuration_window *window);
void on_path_choose(GtkWidget *,configuration_window *window);
void on_configuration_exit(GtkWidget *,configuration_window *window);
void on_configuration_exit(GtkWidget *,configuration_window *window);
int yon_config_save(main_window *widgets);
void yon_config_restore(main_window *widgets);

@ -137,6 +137,8 @@
#define INSTALLATION_OPTIONS_LABEL _("Installation options")
#define CONFIGURATION_MODE_LABEL _("Configuration mode")
#define CONFIGURATION_MODE_STATUS_LABEL _("Attention! Configuration mode was enabled!")
#define CONFIG_PATH_LABEL(path) yon_char_unite(_("Configuration will be saved in configuration file")," ",path,NULL)
#define ERROR_HEAD_LABEL _("Error")
#define ERROR_LABEL _("Error has occured while installation process")
@ -208,4 +210,5 @@
#define ENABLED_KERNEL_MISSING_LABEL _("No kernel was enabled")
#define CONFIGURATION_MODE_TITLE_LABEL _("Choose installation configuration file")
#define CONFIGURATION_MODE_TITLE_LABEL _("Choose installation configuration file")
#define SAVE_AND_EXIT_LABEL _("Save and exit")

@ -0,0 +1,244 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.24"/>
<object class="GtkWindow" id="MainWindow">
<property name="width-request">400</property>
<property name="height-request">250</property>
<property name="can-focus">False</property>
<property name="modal">True</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="StatusBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-left">5</property>
<property name="margin-right">5</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">15</property>
<property name="margin-bottom">15</property>
<property name="spacing">15</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="valign">start</property>
<property name="icon-name">com.ublinux.ubinstall-gtk</property>
<property name="icon_size">6</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Choose a path for configuration file</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="spacing">5</property>
<child>
<object class="GtkSwitch">
<property name="visible">True</property>
<property name="can-focus">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Automatically</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="spacing">5</property>
<child>
<object class="GtkComboBoxText">
<property name="visible">True</property>
<property name="can-focus">False</property>
<items>
<item translatable="yes">Device</item>
<item translatable="yes">Folder</item>
<item translatable="yes">ISO-image</item>
</items>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="PathEntry">
<property name="visible">True</property>
<property name="can-focus">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="PathButton">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="image">image1</property>
<style>
<class name="thin"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="PathButton1">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="image">image2</property>
<style>
<class name="thin"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
<child type="titlebar">
<object class="GtkHeaderBar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child type="title">
<object class="GtkLabel" id="headerTopic">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-bottom">2</property>
<property name="label" translatable="yes">UBLinux installation</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
</child>
<child>
<object class="GtkButton" id="CancelButton">
<property name="label" translatable="yes">Cancel</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
</object>
</child>
<child>
<object class="GtkButton" id="ChooseButton">
<property name="label" translatable="yes">Choose</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkImage" id="image1">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">com.ublinux.libublsettingsui-gtk3.zoom-symbolic</property>
</object>
<object class="GtkImage" id="image2">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">com.ublinux.libublsettingsui-gtk3.increase-symbolic</property>
</object>
</interface>

@ -118,6 +118,10 @@ background:transparent;
.boxInfoMessOK{
background-color: #f3f0ac;
}
.boxInfoMessGray{
background-color: darker(@theme_bg_color);
}
.errorBox {
border-width: 2px;
border-color: #ea9999;

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.40.0 -->
<!-- Generated with glade 3.38.2 -->
<interface domain="ubinstall-gtk">
<requires lib="gtk+" version="3.24"/>
<!-- interface-css-provider-path ubinstall-gtk.css -->
@ -448,6 +448,21 @@
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="StatusBox2">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
@ -10575,7 +10590,7 @@ separately into the selected partition.</property>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="position">2</property>
</packing>
</child>
</object>

Loading…
Cancel
Save