Gnc-Prefs: add code to migrate settings from gconf to gsettings at runtime

git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@23273 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
Geert Janssens 2013-10-07 14:32:02 +00:00
parent ce33da254f
commit a807648901
10 changed files with 1535 additions and 3 deletions

View File

@ -484,6 +484,14 @@ AC_CHECK_LIB(xml2, xmlElemDump, [:], [
])
LIBS="$oLIBS"
### --------------------------------------------------------------------------
### LIBXSLT
PKG_CHECK_MODULES(LIBXSLT, libxslt)
AS_SCRUB_INCLUDE(LIBXLT_CFLAGS)
AC_SUBST(LIBXSLT_CFLAGS)
AC_SUBST(LIBXSLT_LIBS)
### --------------------------------------------------------------------------
### Zlib

View File

@ -33,7 +33,9 @@ AM_CPPFLAGS = \
${GUILE_INCS} \
${PYTHON_CPPFLAGS} \
${GLIB_CFLAGS} \
${GTK_CFLAGS}
${GTK_CFLAGS} \
${LIBXML2_CFLAGS} \
${LIBXSLT_CFLAGS}
libgncmod_app_utils_la_SOURCES = \
calculation/expression_parser.c \
@ -97,7 +99,9 @@ libgncmod_app_utils_la_LIBADD = \
${top_builddir}/src/libqof/qof/libgnc-qof.la \
${GTK_LIBS} \
${GUILE_LIBS} \
${GLIB_LIBS}
${GLIB_LIBS} \
${LIBXML2_LIBS} \
${LIBXSLT_LIBS}
if BUILDING_FROM_SCM
swig-app-utils-guile.c: app-utils.i ${top_srcdir}/src/base-typemaps.i
@ -134,9 +138,15 @@ gncscm_DATA = \
config-var.scm \
date-utilities.scm \
hooks.scm \
migrate-prefs.scm \
options.scm \
prefs.scm \
simple-obj.scm
prefsmigrationdir = $(GNC_SHAREDIR)
prefsmigration_DATA = \
make-prefs-migration-script.xsl \
migratable-prefs.xml
noinst_DATA = .scm-links
@ -150,7 +160,8 @@ EXTRA_DIST = \
swig-app-utils-python.c \
app-utils.i \
${gncmod_DATA} \
${gncscm_DATA}
${gncscm_DATA} \
${prefsmigration_DATA}
if OS_WIN32
libgncmod_app_utils_la_SOURCES += gnc-help-utils.c

View File

@ -21,6 +21,7 @@
(use-modules (gnucash main)) ;; FIXME: delete after we finish modularizing.
(use-modules (gnucash gnc-module))
(use-modules (ice-9 syncase))
(use-modules (migrate-prefs))
;; Guile 2 needs to find the symbols from the c module at compile time already
(cond-expand
@ -276,6 +277,10 @@
(export simple-obj-from-list)
(export make-simple-obj)
;; migrate-prefs.scm
(re-export migration-prepare)
(re-export migration-cleanup)
(define gnc:*kvp-option-path* (list KVP-OPTION-PATH))
(export gnc:*kvp-option-path*)

View File

@ -27,14 +27,29 @@
#include <stdio.h>
#include <string.h>
#include "gnc-gsettings.h"
#include "gnc-path.h"
#include "guile-mappings.h"
#include <libguile.h>
#include "libqof/qof/qof.h"
#include "gnc-prefs-p.h"
#include <libxml/xmlmemory.h>
#include <libxml/debugXML.h>
#include <libxml/HTMLtree.h>
#include <libxml/xmlIO.h>
#include <libxml/xinclude.h>
#include <libxml/catalog.h>
#include <libxslt/xslt.h>
#include <libxslt/xsltInternals.h>
#include <libxslt/transform.h>
#include <libxslt/xsltutils.h>
#define CLIENT_TAG "%s-%s-client"
#define NOTIFY_TAG "%s-%s-notify_id"
static GHashTable *schema_hash = NULL;
static const gchar *gsettings_prefix;
static xmlExternalEntityLoader defaultEntityLoader = NULL;
/* This static indicates the debugging module that this .o belongs to. */
static QofLogModule log_module = G_LOG_DOMAIN;
@ -276,6 +291,7 @@ gnc_gsettings_set_bool (const gchar *schema,
GSettings *schema_ptr = gnc_gsettings_get_schema_ptr (schema);
g_return_val_if_fail (G_IS_SETTINGS (schema_ptr), FALSE);
ENTER("schema: %s, key: %s", schema, key);
if (gnc_gsettings_is_valid_key (schema_ptr, key))
{
result = g_settings_set_boolean (schema_ptr, key, value);
@ -285,6 +301,7 @@ gnc_gsettings_set_bool (const gchar *schema,
else
PERR ("Invalid key %s for schema %s", key, schema);
LEAVE("result %i", result);
return result;
}
@ -387,6 +404,7 @@ gnc_gsettings_set_string (const gchar *schema,
GSettings *schema_ptr = gnc_gsettings_get_schema_ptr (schema);
g_return_val_if_fail (G_IS_SETTINGS (schema_ptr), FALSE);
ENTER("schema: %s, key: %s", schema, key);
if (gnc_gsettings_is_valid_key (schema_ptr, key))
{
result = g_settings_set_string (schema_ptr, key, value);
@ -396,6 +414,7 @@ gnc_gsettings_set_string (const gchar *schema,
else
PERR ("Invalid key %s for schema %s", key, schema);
LEAVE("result %i", result);
return result;
}
@ -529,5 +548,118 @@ void gnc_gsettings_load_backend (void)
prefsbackend.set_value = gnc_gsettings_set_value;
prefsbackend.reset = gnc_gsettings_reset;
prefsbackend.reset_group = gnc_gsettings_reset_schema;
LEAVE("Prefsbackend bind = %p", prefsbackend.bind);
}
/* Attempt to migrate preferences from gconf files
to gsettings if not already done so */
/* This snippet is borrowed from the xsltproc source
* and adapted to help the xsl transform find our temporary
* files in $HOME/.gnc-migration-tmp/
*/
static xmlParserInputPtr
xsltprocExternalEntityLoader(const char *URL, const char *ID,
xmlParserCtxtPtr ctxt) {
xmlParserInputPtr ret;
warningSAXFunc warning = NULL;
xmlChar *newURL;
gchar *tmpdir = g_build_filename (g_getenv ("HOME"), ".gnc-migration-tmp", NULL);
int i;
const char *lastsegment = URL;
const char *iter = URL;
while (*iter != 0) {
if (*iter == '/')
lastsegment = iter + 1;
iter++;
}
if ((ctxt != NULL) && (ctxt->sax != NULL)) {
warning = ctxt->sax->warning;
ctxt->sax->warning = NULL;
}
if (defaultEntityLoader != NULL) {
ret = defaultEntityLoader(URL, ID, ctxt);
if (ret != NULL) {
if (warning != NULL)
ctxt->sax->warning = warning;
return(ret);
}
}
newURL = xmlStrdup((const xmlChar *) tmpdir);
newURL = xmlStrcat(newURL, (const xmlChar *) "/");
newURL = xmlStrcat(newURL, (const xmlChar *) lastsegment);
g_free (tmpdir);
if (newURL != NULL) {
ret = defaultEntityLoader((const char *)newURL, ID, ctxt);
if (ret != NULL) {
if (warning != NULL)
ctxt->sax->warning = warning;
xmlFree(newURL);
return(ret);
}
xmlFree(newURL);
}
if (warning != NULL) {
ctxt->sax->warning = warning;
if (URL != NULL)
warning(ctxt, "failed to load external entity \"%s\"\n", URL);
else if (ID != NULL)
warning(ctxt, "failed to load external entity \"%s\"\n", ID);
}
return(NULL);
}
void gnc_gsettings_migrate_from_gconf (void)
{
gchar *pkgdatadir, *stylesheet, *input, *output;
gchar *migr_dir;
SCM migr_script;
SCM result = scm_c_eval_string ("(use-modules (gnucash app-utils))(migration-prepare)");
xsltStylesheetPtr stylesheetptr = NULL;
xmlDocPtr inputxml, transformedxml;
FILE *outfile;
pkgdatadir = gnc_path_get_pkgdatadir();
stylesheet = g_build_filename(pkgdatadir, "make-prefs-migration-script.xsl", NULL);
input = g_build_filename(pkgdatadir, "migratable-prefs.xml", NULL);
migr_dir = g_build_filename(g_getenv ("HOME"), ".gnc-migration-tmp", NULL);
output = g_build_filename(migr_dir, "migrate-prefs-user.scm", NULL);
xmlSubstituteEntitiesDefault(1);
xmlLoadExtDtdDefaultValue = 1;
defaultEntityLoader = xmlGetExternalEntityLoader();
xmlSetExternalEntityLoader(xsltprocExternalEntityLoader);
stylesheetptr = xsltParseStylesheetFile((const xmlChar *)stylesheet);
inputxml = xmlParseFile(input);
transformedxml = xsltApplyStylesheet(stylesheetptr, inputxml, NULL);
outfile = fopen(output, "w");
xsltSaveResultToFile(outfile, transformedxml, stylesheetptr);
fclose(outfile);
migr_script = scm_from_locale_string (output);
scm_primitive_load (migr_script);
result = scm_c_eval_string ("(use-modules (migrate-prefs-user))(run-migration)");
xsltFreeStylesheet(stylesheetptr);
xmlFreeDoc(inputxml);
xmlFreeDoc(transformedxml);
xsltCleanupGlobals();
xmlCleanupParser();
result = scm_c_eval_string ("(use-modules (gnucash app-utils))(migration-cleanup)");
g_free (pkgdatadir);
g_free (stylesheet);
g_free (input);
g_free (output);
g_free (migr_dir);
}

View File

@ -595,6 +595,11 @@ void gnc_gsettings_reset_schema (const gchar *schema);
*/
void gnc_gsettings_load_backend (void);
/* Attempt to migrate preferences from gconf files
to gsettings if not already done so */
void gnc_gsettings_migrate_from_gconf (void);
#endif /* GNC_GSETTINGS_H */
/** @} */
/** @} */

View File

@ -15,6 +15,7 @@
#include "gnc-component-manager.h"
#include "gnc-hooks.h"
#include "gnc-exp-parser.h"
#include "gnc-gsettings.h"
GNC_MODULE_API_DECL(libgncmod_app_utils)
@ -77,6 +78,7 @@ libgncmod_app_utils_gnc_module_init(int refcount)
gnc_component_manager_init ();
gnc_hook_add_dangler(HOOK_STARTUP, (GFunc)gnc_exp_parser_init, NULL);
gnc_hook_add_dangler(HOOK_SHUTDOWN, (GFunc)app_utils_shutdown, NULL);
gnc_gsettings_migrate_from_gconf();
}
return TRUE;

View File

@ -0,0 +1,193 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:df="http://dateformats.data">
<xsl:output method="text" encoding="UTF8"/>
<!-- Configure lookup table for date format -->
<xsl:key name="datefmt-lookup" match="df:dateformat" use="df:name"/>
<xsl:variable name="dateformats-top" select="document('')/*/df:dateformats"/>
<!-- Primary template - process each prefence group -->
<xsl:template match="/">
<!-- Write file header -->
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; migrate-prefs.scm
;;; Custom generated script to migrate user preferences from
;;; gconf to gsettings. This should only be run once -
;;; when running GnuCash 2.6.x for the first time.
;;;
;;; Copyright 2013 Geert Janssens &lt;geert@kobaltwit.be&gt;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define-module (migrate-prefs-user))
(use-modules (gnucash core-utils))
;(use-modules (gnucash gnc-module))
;; Guile 2 needs to find the symbols from the c module at compile time already
;(cond-expand
; (guile-2
; (eval-when
; (compile load eval)
; (load-extension "libgnc-core-utils" "scm_init_sw_core_utils_module")))
; (else
; (load-extension "libgnc-core-utils" "scm_init_sw_core_utils_module")))
;(use-modules (sw_core_utils))
(define (run-migration)
<xsl:for-each select="//prefsgroup">
<xsl:if test="document(gconfpath)//entry">
;; Processing preferences in group <xsl:value-of select="gschemaid"/>
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
<xsl:variable name="gconf-top" select="document(gconfpath)/gconf"/>
<xsl:apply-templates select="./pref"/>
</xsl:if>
</xsl:for-each>
(display "Preference migration has finished")(newline)
)
(export run-migration)
</xsl:template>
<!-- Process one prefence -->
<xsl:template match="pref">
<xsl:variable name="gconf-top" select="document(../gconfpath)/gconf"/>
<xsl:apply-templates select="$gconf-top">
<xsl:with-param name="curr-pref" select="."/>
</xsl:apply-templates>
</xsl:template>
<!-- Find the equivalent entry in gconf -->
<xsl:template match="gconf">
<xsl:param name="curr-pref"/>
<xsl:for-each select="entry">
<xsl:if test="@name = $curr-pref/gconfkey">
;; Processing preference <xsl:value-of select="$curr-pref/gschemaname"/>
<xsl:apply-templates select=".">
<xsl:with-param name="curr-pref" select="$curr-pref"/>
</xsl:apply-templates>
</xsl:if>
</xsl:for-each>
</xsl:template>
<!-- Determine next action based on preference type -->
<xsl:template match="entry">
<xsl:param name="curr-pref"/>
<xsl:choose>
<xsl:when test="$curr-pref/gschematype = 'b'">
;; Gconf value (boolean): <xsl:value-of select="./@value"/>
(gnc-prefs-set-bool
; preference group
"<xsl:value-of select="$curr-pref/../gschemaid"/>"
; preference name
"<xsl:value-of select="$curr-pref/gschemaname"/>"
; preference value
<xsl:if test="./@value = 'true'">#t</xsl:if>
<xsl:if test="./@value = 'false'">#f</xsl:if>
)
</xsl:when>
<xsl:when test="$curr-pref/gschematype = 'datefmt'">
;; Gconf value (string): "<xsl:value-of select="./stringvalue"/>" -> gsettings (integer)
(gnc-prefs-set-int
; preference group
"<xsl:value-of select="$curr-pref/../gschemaid"/>"
; preference name
"<xsl:value-of select="$curr-pref/gschemaname"/>"
; preference value
<xsl:apply-templates select="$dateformats-top">
<xsl:with-param name="curr-entry" select="."/>
</xsl:apply-templates>
)
</xsl:when>
<xsl:when test="$curr-pref/gschematype = '(dd)'">
;; Type: pair of decimals (stored in Gconf as [d,d])
; guile command to write pair of decimals value
</xsl:when>
<xsl:when test="$curr-pref/gschematype = 'd'">
;; Gconf value (decimal): <xsl:value-of select="./@value"/>
(gnc-prefs-set-float
; preference group
"<xsl:value-of select="$curr-pref/../gschemaid"/>"
; preference name
"<xsl:value-of select="$curr-pref/gschemaname"/>"
; preference value
<xsl:value-of select="./@value"/>
)
</xsl:when>
<xsl:when test="$curr-pref/gschematype = 'i'">
;; Gconf value (integer): <xsl:value-of select="./@value"/>
(gnc-prefs-set-int
; preference group
"<xsl:value-of select="$curr-pref/../gschemaid"/>"
; preference name
"<xsl:value-of select="$curr-pref/gschemaname"/>"
; preference value
<xsl:value-of select="./@value"/>
)
</xsl:when>
<xsl:when test="$curr-pref/gschematype = 's2b'">
;; Gconf value (string): "<xsl:value-of select="./stringvalue"/>" -> gsettings (boolean)
(let ((suffix (string-delete "<xsl:value-of select="./stringvalue"/>" #\_)))
(gnc-prefs-set-bool
; preference group
"<xsl:value-of select="$curr-pref/../gschemaid"/>"
; preference name
"<xsl:value-of select="$curr-pref/gschemaname"/>"
; preference value
(string-suffix? suffix "<xsl:value-of select="$curr-pref/gschemaname"/>")))
</xsl:when>
<xsl:when test="$curr-pref/gschematype = 's'">
;; Gconf value (string): "<xsl:value-of select="./stringvalue"/>"
(gnc-prefs-set-string
; preference group
"<xsl:value-of select="$curr-pref/../gschemaid"/>"
; preference name
"<xsl:value-of select="$curr-pref/gschemaname"/>"
; preference value
"<xsl:value-of select="./stringvalue"/>"
)
</xsl:when>
<xsl:when test="$curr-pref/gschematype = 'x'">
;; Gconf value (64bit integer): <xsl:value-of select="./@value"/>
(gnc-prefs-set-int64
; preference group
"<xsl:value-of select="$curr-pref/../gschemaid"/>"
; preference name
"<xsl:value-of select="$curr-pref/gschemaname"/>"
; preference value
<xsl:value-of select="./@value"/>
)
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template match="df:dateformats">
<xsl:param name="curr-entry"/>
<xsl:value-of select="key('datefmt-lookup', $curr-entry/stringvalue)/df:index"/>
</xsl:template>
<df:dateformats>
<df:dateformat><df:name>us</df:name><df:index>0</df:index></df:dateformat>
<df:dateformat><df:name>uk</df:name><df:index>1</df:index></df:dateformat>
<df:dateformat><df:name>ce</df:name><df:index>2</df:index></df:dateformat>
<df:dateformat><df:name>iso</df:name><df:index>3</df:index></df:dateformat>
<df:dateformat><df:name>locale</df:name><df:index>4</df:index></df:dateformat>
</df:dateformats>
</xsl:stylesheet>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,108 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; migrate-prefs.scm
;;; Functions used to migrated user preferences from gconf
;;; to gsettings. Note that this module doesn't perform the
;;; migration itself: it merely prepares the environment to
;;; create the actual migration script.
;;;
;;; Copyright 2013 Geert Janssens <geert@kobaltwit.be>
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define-module (migrate-prefs))
(use-modules (gnucash core-utils))
(define gconf-dir (string-append (getenv "HOME") "/.gconf/apps/gnucash"))
(define prefix-length (+ (string-length gconf-dir) 1))
(define migration-dir (string-append (getenv "HOME") "/.gnc-migration-tmp"))
(define (copy-one-file filename)
(let ((stats (stat filename))
(base-name "")
(slash-index 0)
(dest-name ""))
;(display "processing file... ")(display filename)(newline)
(if (eq? (stat:type stats) 'regular)
(begin
(set! base-name (string-drop filename prefix-length))
;(display base-name)(newline)
(set! slash-index (- (string-rindex base-name #\%) 1))
(if (> slash-index 0)
(begin
(set! dest-name (string-take base-name (- (string-rindex base-name #\%) 1)))
(set! dest-name (string-join (string-split dest-name #\/) "-"))
(set! dest-name (string-append migration-dir "/" dest-name ".xml"))
;(format #t "~A -> ~A\n" base-name dest-name)
(copy-file filename dest-name)
))))
(if (eq? (stat:type stats) 'directory)
(apply find copy-one-file (list filename))
)))
(define (directory-files dir)
(if (not (access? dir R_OK))
'()
(let* ((p (opendir dir))
(filelist (do ((file (readdir p) (readdir p))
(ls '()))
((eof-object? file) (closedir p) (reverse! ls))
(if (not (string-suffix? "." file))
(set! ls (cons file ls)))
)))
(sort filelist string<))))
(define (find proc . dirs)
(cond ((pair? dirs)
(for-each proc (map (lambda (x) (string-append (car dirs) "/" x))
(directory-files (car dirs)))))))
(define (finddepth proc . dirs)
(cond ((pair? dirs)
(apply finddepth proc (cdr dirs))
(for-each proc (map (lambda (x) (string-append (car dirs) "/" x))
(directory-files (car dirs)))))))
(define (migration-prepare-internal)
; cleanup first if a previous migration attempt failed to do so
(if (access? migration-dir (logior R_OK W_OK X_OK))
(begin
(format #t "Clear previous tmp dir ~A\n" migration-dir)
(migration-cleanup-internal)))
(mkdir migration-dir)
(format #t "Copy all gconf files to tmp dir ~A\n" migration-dir)
(apply find copy-one-file (list gconf-dir))
)
(define (migration-prepare)
(if (access? gconf-dir R_OK)
(begin
(display "*** GnuCash switched to a new preferences system ***\n")
(display "Attempt to migrate your preferences from the old to the new system\n")
(catch #t
migration-prepare-internal
(lambda args
(display "An error occurred when trying to migrate preferences")))
)))
(define (rmtree args)
(define (zap f)
(let ((rm (if (eq? (stat:type (stat f)) 'directory) rmdir delete-file)))
;(format #t "deleting ~A\n" f)
(catch #t
(lambda () (rm f))
(lambda args (format #t "couldn't delete ~A\n" f)))))
(apply finddepth zap args))
(define (migration-cleanup-internal)
(rmtree (list migration-dir))
(rmdir migration-dir))
(define (migration-cleanup)
(if (access? migration-dir (logior R_OK W_OK X_OK))
(begin
(format #t "Delete tmp dir ~A\n" migration-dir)
(migration-cleanup-internal))))
(export migration-prepare migration-cleanup)

View File

@ -34,3 +34,10 @@
(re-export gnc-scm-log-msg)
(re-export gnc-scm-log-debug)
(re-export gnc-locale-default-iso-currency-code)
(re-export gnc-prefs-set-bool)
(re-export gnc-prefs-set-int)
(re-export gnc-prefs-set-int64)
(re-export gnc-prefs-set-float)
(re-export gnc-prefs-set-string)
(re-export gnc-prefs-set-coords)