/*! \brief Save the schematic file * \par Function Description * This function saves the current schematic file in the toplevel object. * * \param [in,out] toplevel The TOPLEVEL object containing the schematic. * \param [in] filename The file name to save the schematic to. * \param [in,out] err #GError structure for error reporting, or * NULL to disable error reporting * \return 1 on success, 0 on failure. */ int f_save(TOPLEVEL *toplevel, PAGE *page, const char *filename, GError **err) { gchar *backup_filename; gchar *real_filename; gchar *only_filename; gchar *dirname; mode_t saved_umask, mask; struct stat st; GError *tmp_err = NULL; /* Get the real filename and file permissions */ real_filename = follow_symlinks (filename, &tmp_err); if (real_filename == NULL) { g_set_error (err, tmp_err->domain, tmp_err->code, _("Can't get the real filename of %s: %s"), filename, tmp_err->message); return 0; } /* Get the directory in which the real filename lives */ dirname = g_path_get_dirname (real_filename); only_filename = g_path_get_basename(real_filename); /* Do a backup if it's not an undo file backup and it was never saved. */ if (page->saved_since_first_loaded == 0) { if ( (g_file_test (real_filename, G_FILE_TEST_EXISTS)) && (!g_file_test(real_filename, G_FILE_TEST_IS_DIR)) ) { backup_filename = g_strdup_printf("%s%c%s~", dirname, G_DIR_SEPARATOR, only_filename); /* Make the backup file read-write before saving a new one */ if ( g_file_test (backup_filename, G_FILE_TEST_EXISTS) && (! g_file_test (backup_filename, G_FILE_TEST_IS_DIR))) { if (chmod(backup_filename, S_IREAD|S_IWRITE) != 0) { s_log_message (_("Could NOT set previous backup file [%s] read-write\n"), backup_filename); } } if (rename(real_filename, backup_filename) != 0) { s_log_message (_("Can't save backup file: %s."), backup_filename); } else { /* Make the backup file readonly so a 'rm *' command will ask the user before deleting it */ saved_umask = umask(0); mask = (S_IWRITE|S_IWGRP|S_IEXEC|S_IXGRP|S_IXOTH); mask = (~mask)&0777; mask &= ((~saved_umask) & 0777); if (chmod(backup_filename, mask) != 0) { s_log_message (_("Could NOT set backup file [%s] readonly\n"), backup_filename); } umask(saved_umask); } g_free(backup_filename); } } /* If there is not an existing file with that name, compute the * permissions and uid/gid that we will use for the newly-created file. */ if (stat (real_filename, &st) != 0) { struct stat dir_st; int result; /* Use default permissions */ saved_umask = umask(0); st.st_mode = 0666 & ~saved_umask; umask(saved_umask); #ifdef HAVE_CHOWN st.st_uid = getuid (); result = stat (dirname, &dir_st); if (result == 0 && (dir_st.st_mode & S_ISGID)) st.st_gid = dir_st.st_gid; else st.st_gid = getgid (); #endif /* HAVE_CHOWN */ } g_free (dirname); g_free (only_filename); if (o_save (toplevel, s_page_objects (page), real_filename, &tmp_err)) { page->saved_since_first_loaded = 1; /* Reset the last saved timer */ g_get_current_time (&page->last_load_or_save_time); page->ops_since_last_backup = 0; page->do_autosave_backup = 0; /* Restore permissions. */ chmod (real_filename, st.st_mode); #ifdef HAVE_CHOWN if (chown (real_filename, st.st_uid, st.st_gid)) { /* Error occured with chown */ #warning FIXME: What do we do? } #endif g_free (real_filename); return 1; } else { g_set_error (err, tmp_err->domain, tmp_err->code, _("Could NOT save file: %s"), tmp_err->message); g_clear_error (&tmp_err); g_free (real_filename); return 0; } }
/*! \brief Do autosave on all pages that are marked. * \par Function Description * Looks for pages with the do_autosave_backup flag activated and * autosaves them. * * \param [in] w_current The GSCHEM_TOPLEVEL object to search for autosave's. */ void o_autosave_backups(GSCHEM_TOPLEVEL *w_current) { TOPLEVEL *toplevel = w_current->toplevel; GList *iter; PAGE *p_save, *p_current; gchar *backup_filename; gchar *real_filename; gchar *only_filename; gchar *dirname; mode_t saved_umask; mode_t mask; struct stat st; /* save current page */ p_save = toplevel->page_current; for ( iter = geda_list_get_glist( toplevel->pages ); iter != NULL; iter = g_list_next( iter ) ) { p_current = (PAGE *)iter->data; if (p_current->do_autosave_backup == 0) { continue; } if (p_current->ops_since_last_backup != 0) { /* make p_current the current page of toplevel */ s_page_goto (toplevel, p_current); /* Get the real filename and file permissions */ real_filename = follow_symlinks (p_current->page_filename, NULL); if (real_filename == NULL) { s_log_message (_("o_autosave_backups: Can't get the real filename of %s."), p_current->page_filename); } else { /* Get the directory in which the real filename lives */ dirname = g_path_get_dirname (real_filename); only_filename = g_path_get_basename(real_filename); backup_filename = g_strdup_printf("%s%c"AUTOSAVE_BACKUP_FILENAME_STRING, dirname, G_DIR_SEPARATOR, only_filename); /* If there is not an existing file with that name, compute the * permissions and uid/gid that we will use for the newly-created file. */ if (stat (real_filename, &st) != 0) { #if defined(HAVE_GETUID) && defined(HAVE_GETGID) struct stat dir_st; int result; #endif /* Use default permissions */ saved_umask = umask(0); st.st_mode = 0666 & ~saved_umask; umask(saved_umask); #if defined(HAVE_GETUID) && defined(HAVE_GETGID) st.st_uid = getuid (); result = stat (dirname, &dir_st); if (result == 0 && (dir_st.st_mode & S_ISGID)) st.st_gid = dir_st.st_gid; else st.st_gid = getgid (); #endif } g_free (dirname); g_free (only_filename); g_free (real_filename); /* Make the backup file writable before saving a new one */ if ( g_file_test (backup_filename, G_FILE_TEST_EXISTS) && (! g_file_test (backup_filename, G_FILE_TEST_IS_DIR))) { saved_umask = umask(0); if (chmod(backup_filename, (S_IWRITE|S_IWGRP|S_IWOTH) & ((~saved_umask) & 0777)) != 0) { s_log_message (_("Could NOT set previous backup file [%s] read-write\n"), backup_filename); } umask(saved_umask); } if (o_save (toplevel, s_page_objects (toplevel->page_current), backup_filename, NULL)) { p_current->ops_since_last_backup = 0; p_current->do_autosave_backup = 0; /* Make the backup file readonly so a 'rm *' command will ask the user before deleting it */ saved_umask = umask(0); mask = (S_IWRITE|S_IWGRP|S_IEXEC|S_IXGRP|S_IXOTH); mask = (~mask)&0777; mask &= ((~saved_umask) & 0777); if (chmod(backup_filename,mask) != 0) { s_log_message (_("Could NOT set backup file [%s] readonly\n"), backup_filename); } umask(saved_umask); } else { s_log_message (_("Could NOT save backup file [%s]\n"), backup_filename); } g_free (backup_filename); } } } /* restore current page */ s_page_goto (toplevel, p_save); }
/*! \todo Finish function documentation!!! * \brief * \par Function Description * * * <B>flag</B> can be one of the following values: * <DL> * <DT>*</DT><DD>UNDO_ALL * <DT>*</DT><DD>UNDO_VIEWPORT_ONLY * </DL> */ void o_undo_savestate(GSCHEM_TOPLEVEL *w_current, int flag) { TOPLEVEL *toplevel = w_current->toplevel; char *filename = NULL; GList *object_list = NULL; int levels; UNDO *u_current; UNDO *u_current_next; /* save autosave backups if necessary */ o_autosave_backups(w_current); if (w_current->undo_control == FALSE) { return; } if (flag == UNDO_ALL) { /* Increment the number of operations since last backup if auto-save is enabled */ if (toplevel->auto_save_interval != 0) { toplevel->page_current->ops_since_last_backup++; } /* HACK */ /* Before we save the undo state, consolidate nets as necessary */ /* This is where the net consolidation call would have been * triggered before it was removed from o_save_buffer(). */ if (toplevel->net_consolidate == TRUE) o_net_consolidate (toplevel, toplevel->page_current); } if (w_current->undo_type == UNDO_DISK && flag == UNDO_ALL) { filename = g_strdup_printf("%s%cgschem.save%d_%d.sch", tmp_path, G_DIR_SEPARATOR, prog_pid, undo_file_index++); /* Changed from f_save to o_save when adding backup copy creation. */ /* f_save manages the creaton of backup copies. This way, f_save is called only when saving a file, and not when saving an undo backup copy */ o_save (toplevel, s_page_objects (toplevel->page_current), filename, NULL); } else if (w_current->undo_type == UNDO_MEMORY && flag == UNDO_ALL) { object_list = o_glist_copy_all (toplevel, s_page_objects (toplevel->page_current), object_list); } /* Clear Anything above current */ if (toplevel->page_current->undo_current) { s_undo_remove_rest(toplevel, toplevel->page_current->undo_current->next); toplevel->page_current->undo_current->next = NULL; } else { /* undo current is NULL */ s_undo_remove_rest(toplevel, toplevel->page_current->undo_bottom); toplevel->page_current->undo_bottom = NULL; } toplevel->page_current->undo_tos = toplevel->page_current->undo_current; toplevel->page_current->undo_tos = s_undo_add(toplevel->page_current->undo_tos, flag, filename, object_list, toplevel->page_current->left, toplevel->page_current->top, toplevel->page_current->right, toplevel->page_current->bottom, toplevel->page_current->page_control, toplevel->page_current->up); toplevel->page_current->undo_current = toplevel->page_current->undo_tos; if (toplevel->page_current->undo_bottom == NULL) { toplevel->page_current->undo_bottom = toplevel->page_current->undo_tos; } #if DEBUG printf("\n\n---Undo----\n"); s_undo_print_all(toplevel->page_current->undo_bottom); printf("BOTTOM: %s\n", toplevel->page_current->undo_bottom->filename); printf("TOS: %s\n", toplevel->page_current->undo_tos->filename); printf("CURRENT: %s\n", toplevel->page_current->undo_current->filename); printf("----\n"); #endif g_free(filename); /* Now go through and see if we need to free/remove some undo levels */ /* so we stay within the limits */ /* only check history every 10 undo savestates */ if (undo_file_index % 10) { return; } levels = s_undo_levels(toplevel->page_current->undo_bottom); #if DEBUG printf("levels: %d\n", levels); #endif if (levels >= w_current->undo_levels + UNDO_PADDING) { levels = levels - w_current->undo_levels; #if DEBUG printf("Trimming: %d levels\n", levels); #endif u_current = toplevel->page_current->undo_bottom; while(u_current && levels > 0) { u_current_next = u_current->next; if (u_current->filename) { #if DEBUG printf("Freeing: %s\n", u_current->filename); #endif unlink(u_current->filename); g_free(u_current->filename); } if (u_current->object_list) { s_delete_object_glist (toplevel, u_current->object_list); u_current->object_list = NULL; } u_current->next = NULL; u_current->prev = NULL; g_free(u_current); u_current = u_current_next; levels--; } /* Because we use a pad you are always garanteed to never */ /* exhaust the list */ u_current->prev = NULL; toplevel->page_current->undo_bottom = u_current; #if DEBUG printf("New current is: %s\n", u_current->filename); #endif } #if DEBUG printf("\n\n---Undo----\n"); s_undo_print_all(toplevel->page_current->undo_bottom); printf("BOTTOM: %s\n", toplevel->page_current->undo_bottom->filename); printf("TOS: %s\n", toplevel->page_current->undo_tos->filename); printf("CURRENT: %s\n", toplevel->page_current->undo_current->filename); printf("----\n"); #endif }
/*! \todo Finish function documentation!!! * \brief * \par Function Description * * * <B>flag</B> can be one of the following values: * <DL> * <DT>*</DT><DD>UNDO_ALL * <DT>*</DT><DD>UNDO_VIEWPORT_ONLY * </DL> */ void o_undo_savestate (GschemToplevel *w_current, PAGE *page, int flag) { TOPLEVEL *toplevel = gschem_toplevel_get_toplevel (w_current); char *filename = NULL; GList *object_list = NULL; int levels; UNDO *u_current; UNDO *u_current_next; GschemPageView *view = gschem_toplevel_get_current_page_view (w_current); g_return_if_fail (view != NULL); g_return_if_fail (page != NULL); GschemPageGeometry *geometry = gschem_page_view_get_page_geometry (view); /* save autosave backups if necessary */ o_autosave_backups(w_current); if (w_current->undo_control == FALSE) { return; } if (flag == UNDO_ALL) { /* Increment the number of operations since last backup if auto-save is enabled */ if (toplevel->auto_save_interval != 0) { page->ops_since_last_backup++; } /* HACK */ /* Before we save the undo state, consolidate nets as necessary */ /* This is where the net consolidation call would have been * triggered before it was removed from o_save_buffer(). */ if (toplevel->net_consolidate == TRUE) geda_net_object_consolidate (toplevel, page); } if (w_current->undo_type == UNDO_DISK && flag == UNDO_ALL) { filename = g_strdup_printf("%s%cgschem.save%d_%d.sch", tmp_path, G_DIR_SEPARATOR, prog_pid, undo_file_index++); /* Changed from f_save to o_save when adding backup copy creation. */ /* f_save manages the creaton of backup copies. This way, f_save is called only when saving a file, and not when saving an undo backup copy */ o_save (toplevel, s_page_objects (page), filename, NULL); } else if (w_current->undo_type == UNDO_MEMORY && flag == UNDO_ALL) { object_list = o_glist_copy_all (toplevel, s_page_objects (page), object_list); } /* Clear Anything above current */ if (page->undo_current) { s_undo_remove_rest(toplevel, page->undo_current->next); page->undo_current->next = NULL; } else { /* undo current is NULL */ s_undo_remove_rest(toplevel, page->undo_bottom); page->undo_bottom = NULL; } page->undo_tos = page->undo_current; if (geometry != NULL) { page->undo_tos = s_undo_add(page->undo_tos, flag, filename, object_list, (geometry->viewport_left + geometry->viewport_right) / 2, (geometry->viewport_top + geometry->viewport_bottom) / 2, /* scale */ max (((double) abs (geometry->viewport_right - geometry->viewport_left) / geometry->screen_width), ((double) abs (geometry->viewport_top - geometry->viewport_bottom) / geometry->screen_height)), page->page_control, page->up); } else { page->undo_tos = s_undo_add(page->undo_tos, flag, filename, object_list, 0, /* center x */ 0, /* center y */ 0, /* scale */ page->page_control, page->up); } page->undo_current = page->undo_tos; if (page->undo_bottom == NULL) { page->undo_bottom = page->undo_tos; } #if DEBUG printf("\n\n---Undo----\n"); s_undo_print_all(page->undo_bottom); printf("BOTTOM: %s\n", page->undo_bottom->filename); printf("TOS: %s\n", page->undo_tos->filename); printf("CURRENT: %s\n", page->undo_current->filename); printf("----\n"); #endif g_free(filename); /* Now go through and see if we need to free/remove some undo levels */ /* so we stay within the limits */ /* only check history every 10 undo savestates */ if (undo_file_index % 10) { return; } levels = s_undo_levels(page->undo_bottom); #if DEBUG printf("levels: %d\n", levels); #endif if (levels >= w_current->undo_levels + UNDO_PADDING) { levels = levels - w_current->undo_levels; #if DEBUG printf("Trimming: %d levels\n", levels); #endif u_current = page->undo_bottom; while (levels > 0) { /* Because we use a pad you are always guaranteed to never */ /* exhaust the list */ g_assert (u_current != NULL); u_current_next = u_current->next; if (u_current->filename) { #if DEBUG printf("Freeing: %s\n", u_current->filename); #endif unlink(u_current->filename); g_free(u_current->filename); } if (u_current->object_list) { geda_object_list_delete (toplevel, u_current->object_list); u_current->object_list = NULL; } u_current->next = NULL; u_current->prev = NULL; g_free(u_current); u_current = u_current_next; levels--; } g_assert (u_current != NULL); u_current->prev = NULL; page->undo_bottom = u_current; #if DEBUG printf("New current is: %s\n", u_current->filename); #endif } #if DEBUG printf("\n\n---Undo----\n"); s_undo_print_all(page->undo_bottom); printf("BOTTOM: %s\n", page->undo_bottom->filename); printf("TOS: %s\n", page->undo_tos->filename); printf("CURRENT: %s\n", page->undo_current->filename); printf("----\n"); #endif }
/*! \brief Save schematic file and close * \par Function Description * This function will save the current schematic file before closing it. * It also deletes the page_current item in the TOPLEVEL structure. * * \param [in,out] toplevel The TOPLEVEL object containing the schematic. * \param [in] filename The file name to save the schematic to. */ void f_save_close(TOPLEVEL *toplevel, char *filename) { o_save(toplevel, filename); s_page_delete (toplevel, toplevel->page_current); }