Added archiver directory Imported archiver for time travel, location
Tue Dec 19 09:04:00 2000 Bradford Hovinen <hovinen@helixcode.com> * Makefile.am (SUBDIRS): Added archiver directory * archiver/*: Imported archiver for time travel, location management
This commit is contained in:
parent
d2df694453
commit
f089da2886
17 changed files with 4367 additions and 0 deletions
149
archiver/ChangeLog
Normal file
149
archiver/ChangeLog
Normal file
|
@ -0,0 +1,149 @@
|
|||
2000-12-18 Bradford Hovinen <hovinen@helixcode.com>
|
||||
|
||||
* main.c (do_rollback): Support rolling back by steps
|
||||
|
||||
* location.c (location_rollback_backend_by): Implement
|
||||
(location_dump_rollback_data): Support passing steps as well as
|
||||
date
|
||||
|
||||
* config-log.c (config_log_get_rollback_id_by_steps): Implement
|
||||
|
||||
* location.c (write_metadata_file): Don't support writing out the
|
||||
master list any more
|
||||
(save_metadata): Ditto
|
||||
(load_metadata_file): Get backends list from BackendList object
|
||||
(rather than finding it out oneself) if location is toplevel
|
||||
(do_create): Ditto
|
||||
(get_backends_cb): Implement
|
||||
(location_add_backend): Add return values for error conditions
|
||||
|
||||
2000-10-15 Bradford Hovinen <hovinen@helixcode.com>
|
||||
|
||||
* location.c (location_rollback_backends_to): Free id_array when done
|
||||
|
||||
* config-log.c (config_log_reset_filenames): Implement
|
||||
|
||||
* archive.c (archive_set_current_location_id): Move gnome_config_
|
||||
code from set_current_location
|
||||
|
||||
* location.c (location_set_id): Implement
|
||||
|
||||
* main.c (do_rename_location): Implement
|
||||
|
||||
* archive.c (free_location_cb): Don't free locid
|
||||
(archive_get_location):
|
||||
(archive_register_location): Don't strdup the location id
|
||||
(archive_set_current_location_id): Implement
|
||||
|
||||
* main.c: Add options for renaming locations and specifying that
|
||||
backends should be added to or removed from the master list
|
||||
|
||||
* location.c (run_backend_proc): Close all descriptors other than
|
||||
0, 1, 2
|
||||
(location_set_id): Implement
|
||||
(location_set_arg): Free previous locid if exists
|
||||
|
||||
* archive.c (add_location_cb): Implement
|
||||
(archive_set_current_location): Add location change algorithm
|
||||
|
||||
* location.c (location_find_path_from_common_parent): Implement
|
||||
(location_foreach_backend): Implement
|
||||
|
||||
* archive.c (archive_set_current_location): Set
|
||||
archive->current_location_id
|
||||
|
||||
* main.c (do_add_location): Check parent_str for NULL before
|
||||
loading location
|
||||
(do_change_location): Implement
|
||||
(main): Support changing location
|
||||
|
||||
* archive.c (archive_register_location): Implement
|
||||
|
||||
* location.c (do_create):
|
||||
(do_load): Use archive_get_prefix
|
||||
(do_create): Load global location metadata if this location does
|
||||
not inherit from anything
|
||||
(location_get_id): Implement
|
||||
|
||||
* archive.c (archive_get_prefix): Implement
|
||||
|
||||
* location.c (location_get_path): Implement
|
||||
(load_metadata_file): Use archive_is_global
|
||||
(location_set_arg): Set label when setting locid
|
||||
|
||||
* location.[ch]: Make all data members private
|
||||
|
||||
* main.c (main): Get current location name properly, create
|
||||
location iff location id is "default"; bail out with error otherwise
|
||||
(do_remove_location):
|
||||
(do_add_location): Implement
|
||||
|
||||
* config-log.c: Include ctype.h to fix implicit declarations
|
||||
|
||||
* location.c (location_rollback_backends_to): Property initialize i
|
||||
(location_contains): Fix precondition checks
|
||||
(do_create): Remove unused variables
|
||||
|
||||
* archive.c (archive_get_location): Make a copy of locid before
|
||||
working with it
|
||||
(do_load): Fix precondition checks
|
||||
|
||||
* location.c (location_open):
|
||||
(location_new): Make locid const
|
||||
|
||||
* archive.c (archive_get_location): Make locid const
|
||||
|
||||
* location.c (data_delete_cb):
|
||||
(location_delete): Implement
|
||||
|
||||
* config-log.c (config_log_iterate): Implement
|
||||
(config_log_destroy): Make private, pass GtkObject
|
||||
(config_log_delete): Implement
|
||||
|
||||
2000-10-14 Bradford Hovinen <hovinen@helixcode.com>
|
||||
|
||||
* location.c (location_close): Add precondition checks
|
||||
|
||||
* archive.c (archive_destroy): Make private; pass GtkObject
|
||||
|
||||
* location.c (location_add_backend):
|
||||
(location_remove_backend): Add precondition checks
|
||||
(location_destroy): Make private
|
||||
(location_destroy): Pass GtkObject pointer rather than Location
|
||||
pointer; this cleans up assignment in _class_init
|
||||
|
||||
* archive.c (archive_get_current_location_id): Implement
|
||||
(archive_get_current_location): Implement
|
||||
|
||||
2000-09-03 Bradford Hovinen <hovinen@helixcode.com>
|
||||
|
||||
* location.c (location_new): Set is_new
|
||||
(save_metadata): Don't write file unless necessary
|
||||
(save_metadata):
|
||||
(write_metadata_file): Split out file writing into
|
||||
write_metadata_file; write default data file as necessary
|
||||
(location_add_backend):
|
||||
(location_remove_backend): Implement
|
||||
|
||||
* main.c: Add command line arguments for adding/removing locations
|
||||
and backends; move backend id argument into global options
|
||||
|
||||
* location.c (do_load):
|
||||
(load_metadata_file): Split out file parsing logic into
|
||||
load_metadata_file; call load_metadata_file on default file to get
|
||||
contains list if current location doesn't inherit anything
|
||||
(load_metadata_file): g_strdup backend string
|
||||
(load_metadata_file): Warn if top-level location has contains clauses
|
||||
(location_store): Check if the location contains the given backend
|
||||
and try the location it inherits if it doesn't
|
||||
|
||||
* archive.c (archive_load): Set is_global in archive object
|
||||
|
||||
* Makefile.am (Locationmeta{dir|_DATA}): Added commands to install
|
||||
data files
|
||||
(INCLUDES): Added define for LOCATION_DIR
|
||||
|
||||
2000-09-03 Bradford Hovinen <hovinen@helixcode.com>
|
||||
|
||||
* location.c (save_metadata): Write attributes when saving
|
||||
|
26
archiver/Makefile.am
Normal file
26
archiver/Makefile.am
Normal file
|
@ -0,0 +1,26 @@
|
|||
Locationmetadir = $(datadir)/hcm/default
|
||||
Locationmeta_DATA = default-user.xml default-global.xml
|
||||
|
||||
INCLUDES = \
|
||||
-DGNOMELOCALEDIR=\""$(datadir)/locale"\" \
|
||||
-I$(includedir) $(GNOME_INCLUDEDIR) \
|
||||
-DVERSION=\""$(VERSION)"\" \
|
||||
-DCONFIGDIR=\""/etc"\" \
|
||||
-DLOCATION_DIR=\""$(datadir)/hcm/default"\" \
|
||||
$(XML_CFLAGS)
|
||||
|
||||
bin_PROGRAMS = archiver
|
||||
|
||||
archiver_SOURCES = \
|
||||
main.c \
|
||||
archive.c archive.h \
|
||||
location.c location.h \
|
||||
config-log.c config-log.h \
|
||||
backend-list.c backend-list.h \
|
||||
util.c util.h
|
||||
|
||||
archiver_LDADD = \
|
||||
$(GNOME_LIBDIR) \
|
||||
$(GNOMEUI_LIBS) \
|
||||
$(INTLLIBS) \
|
||||
$(GNOME_XML_LIB)
|
34
archiver/TODO
Normal file
34
archiver/TODO
Normal file
|
@ -0,0 +1,34 @@
|
|||
* Add per-user master list
|
||||
* Archiving changes in location metadata
|
||||
* Fix race in lock handling and add timeout support (look in gnome-mime)
|
||||
* Support multiple backends from CLI
|
||||
|
||||
Long-term
|
||||
* Add clustering support:
|
||||
- Add Cluster class inheriting Archive class and overriding path
|
||||
semantics
|
||||
- Change location rollback functionality to send data through to
|
||||
clients if the archive is a cluster
|
||||
* Allow backend specs to identify an order in which they should be applied
|
||||
- Specify this in the master list; have each location look up that
|
||||
information before invoking multiple backends
|
||||
|
||||
Questions
|
||||
|
||||
Done
|
||||
* Global list of configs for a given archive
|
||||
* Location should store backend data in the location where it is valid
|
||||
* Add support for dumping XML to stdout rather than running the backend
|
||||
* Fix bug where EOF not sent through pipe
|
||||
* Changing the name of a location
|
||||
* Adding per-user/global backends
|
||||
- Don't try to write out contains list on toplevel locations
|
||||
- Give error if the user tries to add a backend to a toplevel location
|
||||
* Consistency check on adding and removing backends
|
||||
- Make sure the backend is included in the global metadata list before
|
||||
adding
|
||||
- When removing global and per-user backends, mark the backend
|
||||
"invalid" and exclude from location_foreach_backend,
|
||||
location_rollback_all_to, and location_contains.
|
||||
* Refactor master list into an attribute of Archive
|
||||
* Roll back x number of steps rather than by date
|
523
archiver/archive.c
Normal file
523
archiver/archive.c
Normal file
|
@ -0,0 +1,523 @@
|
|||
/* -*- mode: c; style: linux -*- */
|
||||
|
||||
/* archive.c
|
||||
* Copyright (C) 2000 Helix Code, Inc.
|
||||
*
|
||||
* Written by Bradford Hovinen (hovinen@helixcode.com)
|
||||
*
|
||||
* 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, 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., 59 Temple Place - Suite 330, Boston, MA
|
||||
* 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "archive.h"
|
||||
|
||||
static GtkObjectClass *parent_class;
|
||||
|
||||
enum {
|
||||
ARG_0,
|
||||
ARG_PREFIX
|
||||
};
|
||||
|
||||
static void archive_init (Archive *archive);
|
||||
static void archive_class_init (ArchiveClass *klass);
|
||||
|
||||
static void archive_destroy (GtkObject *object);
|
||||
|
||||
static void archive_set_arg (GtkObject *object,
|
||||
GtkArg *arg,
|
||||
guint arg_id);
|
||||
|
||||
static void archive_get_arg (GtkObject *object,
|
||||
GtkArg *arg,
|
||||
guint arg_id);
|
||||
|
||||
static gboolean do_load (Archive *archive);
|
||||
|
||||
guint
|
||||
archive_get_type (void)
|
||||
{
|
||||
static guint archive_type;
|
||||
|
||||
if (!archive_type) {
|
||||
GtkTypeInfo archive_info = {
|
||||
"Archive",
|
||||
sizeof (Archive),
|
||||
sizeof (ArchiveClass),
|
||||
(GtkClassInitFunc) archive_class_init,
|
||||
(GtkObjectInitFunc) archive_init,
|
||||
(GtkArgSetFunc) NULL,
|
||||
(GtkArgGetFunc) NULL
|
||||
};
|
||||
|
||||
archive_type =
|
||||
gtk_type_unique (gtk_object_get_type (),
|
||||
&archive_info);
|
||||
}
|
||||
|
||||
return archive_type;
|
||||
}
|
||||
|
||||
static void
|
||||
archive_init (Archive *archive)
|
||||
{
|
||||
archive->prefix = NULL;
|
||||
archive->locations = g_tree_new ((GCompareFunc) strcmp);
|
||||
archive->current_location_id = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
archive_class_init (ArchiveClass *klass)
|
||||
{
|
||||
GtkObjectClass *object_class;
|
||||
|
||||
object_class = GTK_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->destroy = archive_destroy;
|
||||
object_class->set_arg = archive_set_arg;
|
||||
object_class->get_arg = archive_get_arg;
|
||||
|
||||
gtk_object_add_arg_type ("Archive::prefix",
|
||||
GTK_TYPE_POINTER,
|
||||
GTK_ARG_READWRITE,
|
||||
ARG_PREFIX);
|
||||
|
||||
parent_class = gtk_type_class (gtk_object_get_type ());
|
||||
}
|
||||
|
||||
static void
|
||||
archive_set_arg (GtkObject *object, GtkArg *arg, guint arg_id)
|
||||
{
|
||||
Archive *archive;
|
||||
|
||||
g_return_if_fail (object != NULL);
|
||||
g_return_if_fail (IS_ARCHIVE (object));
|
||||
g_return_if_fail (arg != NULL);
|
||||
|
||||
archive = ARCHIVE (object);
|
||||
|
||||
switch (arg_id) {
|
||||
case ARG_PREFIX:
|
||||
if (GTK_VALUE_POINTER (*arg) != NULL)
|
||||
archive->prefix = g_strdup (GTK_VALUE_POINTER (*arg));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
archive_get_arg (GtkObject *object, GtkArg *arg, guint arg_id)
|
||||
{
|
||||
Archive *archive;
|
||||
|
||||
g_return_if_fail (object != NULL);
|
||||
g_return_if_fail (IS_ARCHIVE (object));
|
||||
g_return_if_fail (arg != NULL);
|
||||
|
||||
archive = ARCHIVE (object);
|
||||
|
||||
switch (arg_id) {
|
||||
case ARG_PREFIX:
|
||||
GTK_VALUE_POINTER (*arg) = archive->prefix;
|
||||
break;
|
||||
default:
|
||||
arg->type = GTK_TYPE_INVALID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* archive_load:
|
||||
* @is_global: TRUE iff we should load the global archive
|
||||
*
|
||||
* Load either the global or per-user configuration archive
|
||||
*
|
||||
* Return value: Reference to archive
|
||||
**/
|
||||
|
||||
GtkObject *
|
||||
archive_load (gboolean is_global)
|
||||
{
|
||||
GtkObject *object;
|
||||
gchar *prefix;
|
||||
|
||||
if (is_global)
|
||||
prefix = CONFIGDIR "/helix-config";
|
||||
else
|
||||
prefix = g_concat_dir_and_file (g_get_home_dir (),
|
||||
".gnome/helix-config");
|
||||
|
||||
object = gtk_object_new (archive_get_type (),
|
||||
"prefix", prefix,
|
||||
NULL);
|
||||
|
||||
if (is_global)
|
||||
g_free (prefix);
|
||||
|
||||
if (do_load (ARCHIVE (object)) == FALSE) {
|
||||
gtk_object_destroy (object);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ARCHIVE (object)->is_global = is_global;
|
||||
ARCHIVE (object)->backend_list =
|
||||
BACKEND_LIST (backend_list_new (is_global));
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
static gint
|
||||
free_location_cb (gchar *locid, Location *location)
|
||||
{
|
||||
location_close (location);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
archive_destroy (GtkObject *object)
|
||||
{
|
||||
Archive *archive;
|
||||
|
||||
g_return_if_fail (object != NULL);
|
||||
g_return_if_fail (IS_ARCHIVE (object));
|
||||
|
||||
archive = ARCHIVE (object);
|
||||
|
||||
g_tree_traverse (archive->locations,
|
||||
(GTraverseFunc) free_location_cb,
|
||||
G_IN_ORDER,
|
||||
NULL);
|
||||
|
||||
g_tree_destroy (archive->locations);
|
||||
|
||||
if (archive->current_location_id != NULL)
|
||||
g_free (archive->current_location_id);
|
||||
|
||||
GTK_OBJECT_CLASS (parent_class)->destroy (GTK_OBJECT (archive));
|
||||
}
|
||||
|
||||
/**
|
||||
* archive_close:
|
||||
* @archive:
|
||||
*
|
||||
* Closes the given archive handle. Also closes all locations under this
|
||||
* archive.
|
||||
**/
|
||||
|
||||
void
|
||||
archive_close (Archive *archive)
|
||||
{
|
||||
g_return_if_fail (archive != NULL);
|
||||
g_return_if_fail (IS_ARCHIVE (archive));
|
||||
|
||||
gtk_object_destroy (GTK_OBJECT (archive));
|
||||
}
|
||||
|
||||
/**
|
||||
* archive_get_location:
|
||||
* @archive:
|
||||
* @locid:
|
||||
*
|
||||
* Get a reference to the location with the given name.
|
||||
*
|
||||
* Return value: Reference to location, NULL if no such location exists
|
||||
**/
|
||||
|
||||
Location *
|
||||
archive_get_location (Archive *archive, const gchar *locid)
|
||||
{
|
||||
GtkObject *loc_obj;
|
||||
|
||||
g_return_val_if_fail (archive != NULL, NULL);
|
||||
g_return_val_if_fail (IS_ARCHIVE (archive), NULL);
|
||||
g_return_val_if_fail (locid != NULL, NULL);
|
||||
|
||||
loc_obj = g_tree_lookup (archive->locations, locid);
|
||||
|
||||
if (!loc_obj) {
|
||||
loc_obj = location_open (archive, locid);
|
||||
|
||||
if (!loc_obj) return NULL;
|
||||
|
||||
if (loc_obj)
|
||||
g_tree_insert (archive->locations, locid, loc_obj);
|
||||
}
|
||||
|
||||
if (loc_obj) {
|
||||
gtk_object_ref (loc_obj);
|
||||
return LOCATION (loc_obj);
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* archive_register_location:
|
||||
* @archive:
|
||||
* @location:
|
||||
*
|
||||
* Register a location with the archive; invoked by location_new
|
||||
**/
|
||||
|
||||
void
|
||||
archive_register_location (Archive *archive, Location *location)
|
||||
{
|
||||
g_return_if_fail (archive != NULL);
|
||||
g_return_if_fail (IS_ARCHIVE (archive));
|
||||
g_return_if_fail (location != NULL);
|
||||
g_return_if_fail (IS_LOCATION (location));
|
||||
|
||||
g_tree_insert (archive->locations,
|
||||
location_get_id (location),
|
||||
location);
|
||||
}
|
||||
|
||||
/**
|
||||
* archive_unregister_location:
|
||||
* @archive:
|
||||
* @location:
|
||||
*
|
||||
* Unregisters a location from the archive
|
||||
**/
|
||||
|
||||
void
|
||||
archive_unregister_location (Archive *archive, Location *location)
|
||||
{
|
||||
g_return_if_fail (archive != NULL);
|
||||
g_return_if_fail (IS_ARCHIVE (archive));
|
||||
g_return_if_fail (location != NULL);
|
||||
g_return_if_fail (IS_LOCATION (location));
|
||||
|
||||
/* FIXME: We might be screwing things up here if we're traversing... */
|
||||
g_tree_remove (archive->locations, location);
|
||||
}
|
||||
|
||||
/**
|
||||
* archive_get_current_location:
|
||||
* @archive: object
|
||||
*
|
||||
* Convenience function to get a pointer to the current location
|
||||
*
|
||||
* Return value: Pointer to current location
|
||||
**/
|
||||
|
||||
Location *
|
||||
archive_get_current_location (Archive *archive)
|
||||
{
|
||||
g_return_val_if_fail (archive != NULL, NULL);
|
||||
g_return_val_if_fail (IS_ARCHIVE (archive), NULL);
|
||||
|
||||
return archive_get_location (archive,
|
||||
archive_get_current_location_id
|
||||
(archive));
|
||||
}
|
||||
|
||||
static int
|
||||
add_location_cb (Location *location, gchar *backend_id, GList *backends)
|
||||
{
|
||||
g_list_insert (backends, backend_id, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* archive_set_current_location:
|
||||
* @archive: object
|
||||
* @location: Location to which to set archive
|
||||
*
|
||||
* Set the current location in an archive to the location given; apply
|
||||
* configuration for the new location to all backends necessary
|
||||
**/
|
||||
|
||||
void
|
||||
archive_set_current_location (Archive *archive, Location *location)
|
||||
{
|
||||
GList *location_path, *backends;
|
||||
Location *old_location = archive_get_current_location (archive);
|
||||
|
||||
g_return_if_fail (archive != NULL);
|
||||
g_return_if_fail (IS_ARCHIVE (archive));
|
||||
g_return_if_fail (location != NULL);
|
||||
g_return_if_fail (IS_LOCATION (location));
|
||||
|
||||
archive_set_current_location_id (archive, location_get_id (location));
|
||||
|
||||
location_path = location_find_path_from_common_parent
|
||||
(location, old_location);
|
||||
|
||||
backends = g_list_append (NULL, NULL);
|
||||
|
||||
while (location_path != NULL) {
|
||||
if (location_path->data != NULL) {
|
||||
location_foreach_backend
|
||||
(LOCATION (location_path->data),
|
||||
(LocationBackendCB) add_location_cb,
|
||||
backends);
|
||||
}
|
||||
|
||||
location_path = location_path->next;
|
||||
}
|
||||
|
||||
location_rollback_backends_to (location, NULL, backends->next, TRUE);
|
||||
|
||||
g_list_free (backends);
|
||||
}
|
||||
|
||||
/**
|
||||
* archive_set_current_location_id:
|
||||
* @archive:
|
||||
* @name:
|
||||
*
|
||||
* Sets the current location's name, but does not invoke any rollback
|
||||
**/
|
||||
|
||||
void
|
||||
archive_set_current_location_id (Archive *archive, const gchar *locid)
|
||||
{
|
||||
g_return_if_fail (archive != NULL);
|
||||
g_return_if_fail (IS_ARCHIVE (archive));
|
||||
g_return_if_fail (locid != NULL);
|
||||
|
||||
if (archive->current_location_id != NULL)
|
||||
g_free (archive->current_location_id);
|
||||
|
||||
archive->current_location_id = g_strdup (locid);
|
||||
|
||||
if (archive->is_global)
|
||||
gnome_config_push_prefix ("=" LOCATION_DIR "=");
|
||||
else
|
||||
gnome_config_push_prefix ("helix-config/");
|
||||
|
||||
gnome_config_set_string ("config/current/location",
|
||||
archive->current_location_id);
|
||||
|
||||
gnome_config_pop_prefix ();
|
||||
gnome_config_sync ();
|
||||
}
|
||||
|
||||
/**
|
||||
* archive_get_current_location_id:
|
||||
* @archive: object
|
||||
*
|
||||
* Get the name of the current location
|
||||
*
|
||||
* Return value: String containing current location, should not be freed
|
||||
**/
|
||||
|
||||
const gchar *
|
||||
archive_get_current_location_id (Archive *archive)
|
||||
{
|
||||
g_return_val_if_fail (archive != NULL, NULL);
|
||||
g_return_val_if_fail (IS_ARCHIVE (archive), NULL);
|
||||
|
||||
if (archive->current_location_id == NULL) {
|
||||
if (archive->is_global)
|
||||
gnome_config_push_prefix ("=" LOCATION_DIR "=");
|
||||
else
|
||||
gnome_config_push_prefix ("helix-config/");
|
||||
|
||||
archive->current_location_id =
|
||||
gnome_config_get_string
|
||||
("config/current/location=default");
|
||||
|
||||
gnome_config_pop_prefix ();
|
||||
}
|
||||
|
||||
return archive->current_location_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* archive_get_prefix:
|
||||
* @archive:
|
||||
*
|
||||
* Get the prefix for locations in this archive
|
||||
*
|
||||
* Return value: String containing prefix; should not be freed
|
||||
**/
|
||||
|
||||
const gchar *
|
||||
archive_get_prefix (Archive *archive)
|
||||
{
|
||||
g_return_val_if_fail (archive != NULL, FALSE);
|
||||
g_return_val_if_fail (IS_ARCHIVE (archive), FALSE);
|
||||
|
||||
return archive->prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* archive_is_global:
|
||||
* @archive:
|
||||
*
|
||||
* Tell whether the archive is global or per-user
|
||||
*
|
||||
* Return value: TRUE if global, FALSE if per-user
|
||||
**/
|
||||
|
||||
gboolean
|
||||
archive_is_global (Archive *archive)
|
||||
{
|
||||
g_return_val_if_fail (archive != NULL, FALSE);
|
||||
g_return_val_if_fail (IS_ARCHIVE (archive), FALSE);
|
||||
|
||||
return archive->is_global;
|
||||
}
|
||||
|
||||
/**
|
||||
* archive_get_backend_list:
|
||||
* @archive:
|
||||
*
|
||||
* Get the master backend list for this archive
|
||||
*
|
||||
* Return value: Reference to the master backend list
|
||||
**/
|
||||
|
||||
BackendList *
|
||||
archive_get_backend_list (Archive *archive)
|
||||
{
|
||||
g_return_val_if_fail (archive != NULL, FALSE);
|
||||
g_return_val_if_fail (IS_ARCHIVE (archive), FALSE);
|
||||
|
||||
return archive->backend_list;
|
||||
}
|
||||
|
||||
/* Load the archive information from disk; return TRUE on success and FALSE on
|
||||
* failure
|
||||
*/
|
||||
|
||||
static gboolean
|
||||
do_load (Archive *archive)
|
||||
{
|
||||
gint ret = 0;
|
||||
|
||||
g_return_val_if_fail (archive != NULL, FALSE);
|
||||
g_return_val_if_fail (IS_ARCHIVE (archive), FALSE);
|
||||
g_return_val_if_fail (archive->prefix != NULL, FALSE);
|
||||
|
||||
if (g_file_test (archive->prefix, G_FILE_TEST_ISDIR) == FALSE)
|
||||
ret = mkdir (archive->prefix, S_IREAD | S_IWRITE | S_IEXEC);
|
||||
|
||||
if (ret == -1) return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
77
archiver/archive.h
Normal file
77
archiver/archive.h
Normal file
|
@ -0,0 +1,77 @@
|
|||
/* -*- mode: c; style: linux -*- */
|
||||
|
||||
/* archive.h
|
||||
* Copyright (C) 2000 Helix Code, Inc.
|
||||
*
|
||||
* Written by Bradford Hovinen (hovinen@helixcode.com)
|
||||
*
|
||||
* 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, 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., 59 Temple Place - Suite 330, Boston, MA
|
||||
* 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef __ARCHIVE_H
|
||||
#define __ARCHIVE_H
|
||||
|
||||
#include <gnome.h>
|
||||
|
||||
#include "location.h"
|
||||
#include "backend-list.h"
|
||||
|
||||
#define ARCHIVE(obj) GTK_CHECK_CAST (obj, archive_get_type (), Archive)
|
||||
#define ARCHIVE_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, archive_get_type (), ArchiveClass)
|
||||
#define IS_ARCHIVE(obj) GTK_CHECK_TYPE (obj, archive_get_type ())
|
||||
|
||||
typedef struct _ArchiveClass ArchiveClass;
|
||||
|
||||
struct _Archive
|
||||
{
|
||||
GtkObject object;
|
||||
|
||||
gchar *prefix;
|
||||
GTree *locations;
|
||||
gboolean is_global;
|
||||
|
||||
gchar *current_location_id;
|
||||
|
||||
BackendList *backend_list;
|
||||
};
|
||||
|
||||
struct _ArchiveClass
|
||||
{
|
||||
GtkObjectClass parent;
|
||||
};
|
||||
|
||||
guint archive_get_type (void);
|
||||
|
||||
GtkObject *archive_load (gboolean is_global);
|
||||
|
||||
void archive_close (Archive *archive);
|
||||
|
||||
Location *archive_get_location (Archive *archive, const gchar *location);
|
||||
void archive_register_location (Archive *archive, Location *location);
|
||||
void archive_unregister_location (Archive *archive, Location *location);
|
||||
|
||||
Location *archive_get_current_location (Archive *archive);
|
||||
void archive_set_current_location (Archive *archive, Location *location);
|
||||
|
||||
const gchar *archive_get_current_location_id (Archive *archive);
|
||||
void archive_set_current_location_id (Archive *archive, const gchar *locid);
|
||||
|
||||
const gchar *archive_get_prefix (Archive *archive);
|
||||
gboolean archive_is_global (Archive *archive);
|
||||
|
||||
BackendList *archive_get_backend_list (Archive *archive);
|
||||
|
||||
#endif /* __ARCHIVE */
|
77
archiver/archiver-spec
Normal file
77
archiver/archiver-spec
Normal file
|
@ -0,0 +1,77 @@
|
|||
Rollback archiving internals
|
||||
Copyright (C) 2000 Helix Code, Inc.
|
||||
Written by Bradford Hovinen <hovinen@helixcode.com>
|
||||
|
||||
1. Directory format
|
||||
|
||||
Diagram:
|
||||
|
||||
+ toplevel
|
||||
|-+ Location 1
|
||||
| |- CCYYMMDD-hhmm-<id>.xml
|
||||
| | .
|
||||
| | .
|
||||
| | .
|
||||
| |- metadata.log:
|
||||
| | [<id> <date> <time> <backend> ] ^
|
||||
| | [ . ] | Time
|
||||
| | [ . ] |
|
||||
| | [ . ] |
|
||||
| \- metadata.xml:
|
||||
| [... ]
|
||||
| [<inherits>location</inherits> ]
|
||||
| [<valid-config>backend</valid-config> ]
|
||||
| [... ]
|
||||
|-+ Location 2
|
||||
| ...
|
||||
|
||||
There is one toplevel directory for each archive. This directory
|
||||
contains one or more location directories. Each location directory
|
||||
must contain two files: an XML file describing the location and a log
|
||||
of changes made in that location. Each change corresponds to an XML
|
||||
file containing a snapshot of the configuration as modified. There is
|
||||
one XML file per backend. Each change has an id number that is
|
||||
incremented atomicall by the archiving script when it stores
|
||||
configuration changes. The id number, as well as the date and time of
|
||||
storage, form a filename that uniquely identifies each configuration
|
||||
change. The archiving script must also store in the log file a line
|
||||
with the id number, date and time of storage, and backend used
|
||||
whenever it stores XML data. New entries are stored at the head of the
|
||||
file, so that during rollback, the file may be foreward scanned to
|
||||
find the appropriate identifier for the configuration file. The
|
||||
per-location XML configuration file contains information on what the
|
||||
location's parent is and what configurations that location defines.
|
||||
|
||||
For now, the backend shall be referred to by its executable name. When
|
||||
the backends gain CORBA interfaces, I suggest that the OAF id be used
|
||||
instead. This reduces the problem of setting a backend's configuration
|
||||
to a simple object activation and method invocation. The OAF id may
|
||||
also be used to resolve the backend's human-readable name.
|
||||
|
||||
2. Meta-configuration details
|
||||
|
||||
In order that this system be complete, there must be a way to
|
||||
ascertain the current location and to roll back changes in location. I
|
||||
propose that there be a special archive in the configuration hierarchy
|
||||
that contains location history in the same format as other
|
||||
locations. The archiver can then be a single script that accepts
|
||||
command-line arguments describing the request action: `archive this
|
||||
data', `roll back this backend's configuration', and `switch to this
|
||||
location'. It then handles all the details of interfacing with the
|
||||
archive and applying the changes in the correct order. Conceptually,
|
||||
the archiver becomes a backend in and of itself, where the frontend is
|
||||
located in the GUI of HCM. It would therefore be adviseable to use the
|
||||
same standards for the archiver as for other backends and hence make
|
||||
it a CORBA service, where the tool-specific interface is as described
|
||||
above.
|
||||
|
||||
3. Future directions
|
||||
|
||||
The metafile log structure may run into scalability problems for
|
||||
installations have have been in place for a long time. An alternative
|
||||
structure that uses binary indexing might be in order. A command line
|
||||
utility (with GUI interface) could be written to recover the file in
|
||||
the case of corruption; such a utility could simply introspect each of
|
||||
the XML files in a directory. Provided that each XML file contains
|
||||
enough information to create a file entry, which is trivial, recovery
|
||||
is assured.
|
305
archiver/backend-list.c
Normal file
305
archiver/backend-list.c
Normal file
|
@ -0,0 +1,305 @@
|
|||
/* -*- mode: c; style: linux -*- */
|
||||
|
||||
/* backend-list.c
|
||||
* Copyright (C) 2000 Helix Code, Inc.
|
||||
*
|
||||
* Written by Bradford Hovinen <hovinen@helixcode.com>
|
||||
*
|
||||
* 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, 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., 59 Temple Place - Suite 330, Boston, MA
|
||||
* 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include <parser.h>
|
||||
#include <tree.h>
|
||||
|
||||
#include "backend-list.h"
|
||||
|
||||
enum {
|
||||
ARG_0,
|
||||
ARG_IS_GLOBAL
|
||||
};
|
||||
|
||||
struct _BackendListPrivate
|
||||
{
|
||||
gboolean is_global;
|
||||
gchar *filename;
|
||||
GList *backend_ids;
|
||||
};
|
||||
|
||||
static GtkObjectClass *parent_class;
|
||||
|
||||
static void backend_list_init (BackendList *backend_list);
|
||||
static void backend_list_class_init (BackendListClass *class);
|
||||
|
||||
static void backend_list_set_arg (GtkObject *object,
|
||||
GtkArg *arg,
|
||||
guint arg_id);
|
||||
static void backend_list_get_arg (GtkObject *object,
|
||||
GtkArg *arg,
|
||||
guint arg_id);
|
||||
|
||||
static void backend_list_finalize (GtkObject *object);
|
||||
|
||||
static void do_load (BackendList *backend_list);
|
||||
static void do_save (BackendList *backend_list);
|
||||
|
||||
guint
|
||||
backend_list_get_type (void)
|
||||
{
|
||||
static guint backend_list_type = 0;
|
||||
|
||||
if (!backend_list_type) {
|
||||
GtkTypeInfo backend_list_info = {
|
||||
"BackendList",
|
||||
sizeof (BackendList),
|
||||
sizeof (BackendListClass),
|
||||
(GtkClassInitFunc) backend_list_class_init,
|
||||
(GtkObjectInitFunc) backend_list_init,
|
||||
(GtkArgSetFunc) NULL,
|
||||
(GtkArgGetFunc) NULL
|
||||
};
|
||||
|
||||
backend_list_type =
|
||||
gtk_type_unique (gtk_object_get_type (),
|
||||
&backend_list_info);
|
||||
}
|
||||
|
||||
return backend_list_type;
|
||||
}
|
||||
|
||||
static void
|
||||
backend_list_init (BackendList *backend_list)
|
||||
{
|
||||
backend_list->p = g_new0 (BackendListPrivate, 1);
|
||||
}
|
||||
|
||||
static void
|
||||
backend_list_class_init (BackendListClass *class)
|
||||
{
|
||||
GtkObjectClass *object_class;
|
||||
|
||||
gtk_object_add_arg_type ("BackendList::is-global",
|
||||
GTK_TYPE_INT,
|
||||
GTK_ARG_CONSTRUCT_ONLY | GTK_ARG_READWRITE,
|
||||
ARG_IS_GLOBAL);
|
||||
|
||||
object_class = GTK_OBJECT_CLASS (class);
|
||||
object_class->finalize = backend_list_finalize;
|
||||
object_class->set_arg = backend_list_set_arg;
|
||||
object_class->get_arg = backend_list_get_arg;
|
||||
|
||||
parent_class = GTK_OBJECT_CLASS
|
||||
(gtk_type_class (gtk_object_get_type ()));
|
||||
}
|
||||
|
||||
static void
|
||||
backend_list_set_arg (GtkObject *object, GtkArg *arg, guint arg_id)
|
||||
{
|
||||
BackendList *backend_list;
|
||||
|
||||
g_return_if_fail (object != NULL);
|
||||
g_return_if_fail (IS_BACKEND_LIST (object));
|
||||
|
||||
backend_list = BACKEND_LIST (object);
|
||||
|
||||
switch (arg_id) {
|
||||
case ARG_IS_GLOBAL:
|
||||
backend_list->p->is_global = GTK_VALUE_INT (*arg);
|
||||
|
||||
backend_list->p->filename = backend_list->p->is_global ?
|
||||
LOCATION_DIR "/default-global.xml" :
|
||||
LOCATION_DIR "/default-user.xml";
|
||||
do_load (backend_list);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
g_warning ("Bad argument set");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
backend_list_get_arg (GtkObject *object, GtkArg *arg, guint arg_id)
|
||||
{
|
||||
BackendList *backend_list;
|
||||
|
||||
g_return_if_fail (object != NULL);
|
||||
g_return_if_fail (IS_BACKEND_LIST (object));
|
||||
|
||||
backend_list = BACKEND_LIST (object);
|
||||
|
||||
switch (arg_id) {
|
||||
case ARG_IS_GLOBAL:
|
||||
GTK_VALUE_INT (*arg) = backend_list->p->is_global;
|
||||
break;
|
||||
|
||||
default:
|
||||
g_warning ("Bad argument get");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
backend_list_finalize (GtkObject *object)
|
||||
{
|
||||
BackendList *backend_list;
|
||||
|
||||
g_return_if_fail (object != NULL);
|
||||
g_return_if_fail (IS_BACKEND_LIST (object));
|
||||
|
||||
backend_list = BACKEND_LIST (object);
|
||||
|
||||
g_list_foreach (backend_list->p->backend_ids, (GFunc) g_free, NULL);
|
||||
g_list_free (backend_list->p->backend_ids);
|
||||
g_free (backend_list->p);
|
||||
|
||||
GTK_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
GtkObject *
|
||||
backend_list_new (gboolean is_global)
|
||||
{
|
||||
return gtk_object_new (backend_list_get_type (),
|
||||
"is-global", is_global,
|
||||
NULL);
|
||||
}
|
||||
|
||||
gboolean
|
||||
backend_list_contains (BackendList *backend_list, gchar *backend_id)
|
||||
{
|
||||
g_return_val_if_fail (backend_list != NULL, FALSE);
|
||||
g_return_val_if_fail (IS_BACKEND_LIST (backend_list), FALSE);
|
||||
|
||||
return (g_list_find (backend_list->p->backend_ids, backend_id) !=
|
||||
NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* backend_list_foreach:
|
||||
* @backend_list:
|
||||
* @callback:
|
||||
* @data:
|
||||
*
|
||||
* Iterates through all the backends, invoking the callback given and aborting
|
||||
* if any callback returns a nonzero value
|
||||
*
|
||||
* Return value: TRUE iff no callback issued a nonzero value, FALSE otherwise
|
||||
**/
|
||||
|
||||
gboolean
|
||||
backend_list_foreach (BackendList *backend_list, BackendCB callback,
|
||||
gpointer data)
|
||||
{
|
||||
GList *node;
|
||||
|
||||
g_return_val_if_fail (backend_list != NULL, FALSE);
|
||||
g_return_val_if_fail (IS_BACKEND_LIST (backend_list), FALSE);
|
||||
g_return_val_if_fail (callback != NULL, FALSE);
|
||||
|
||||
for (node = backend_list->p->backend_ids; node; node = node->next)
|
||||
if (callback (backend_list, node->data, data)) return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
backend_list_add (BackendList *backend_list, gchar *backend_id)
|
||||
{
|
||||
g_return_if_fail (backend_list != NULL);
|
||||
g_return_if_fail (IS_BACKEND_LIST (backend_list));
|
||||
g_return_if_fail (backend_id != NULL);
|
||||
|
||||
backend_list->p->backend_ids =
|
||||
g_list_prepend (backend_list->p->backend_ids, backend_id);
|
||||
}
|
||||
|
||||
void
|
||||
backend_list_remove (BackendList *backend_list, gchar *backend_id)
|
||||
{
|
||||
g_return_if_fail (backend_list != NULL);
|
||||
g_return_if_fail (IS_BACKEND_LIST (backend_list));
|
||||
g_return_if_fail (backend_id != NULL);
|
||||
|
||||
backend_list->p->backend_ids =
|
||||
g_list_remove (backend_list->p->backend_ids, backend_id);
|
||||
}
|
||||
|
||||
void
|
||||
backend_list_save (BackendList *backend_list)
|
||||
{
|
||||
g_return_if_fail (backend_list != NULL);
|
||||
g_return_if_fail (IS_BACKEND_LIST (backend_list));
|
||||
|
||||
do_save (backend_list);
|
||||
}
|
||||
|
||||
static void
|
||||
do_load (BackendList *backend_list)
|
||||
{
|
||||
xmlNodePtr root_node, node;
|
||||
xmlDocPtr doc;
|
||||
GList *list_tail = NULL;
|
||||
gchar *contains_str;
|
||||
|
||||
doc = xmlParseFile (backend_list->p->filename);
|
||||
if (doc == NULL) return;
|
||||
root_node = xmlDocGetRootElement (doc);
|
||||
|
||||
for (node = root_node->childs; node; node = node->next) {
|
||||
if (!strcmp (node->name, "contains")) {
|
||||
contains_str = xmlGetProp (node, "backend");
|
||||
|
||||
if (contains_str != NULL) {
|
||||
contains_str = g_strdup (contains_str);
|
||||
list_tail = g_list_append (list_tail,
|
||||
contains_str);
|
||||
if (backend_list->p->backend_ids == NULL)
|
||||
backend_list->p->backend_ids =
|
||||
list_tail;
|
||||
else
|
||||
list_tail = list_tail->next;
|
||||
} else {
|
||||
g_warning ("Bad backends list: " \
|
||||
"contains element with no " \
|
||||
"backend attribute");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
do_save (BackendList *backend_list)
|
||||
{
|
||||
xmlNodePtr root_node, child_node;
|
||||
xmlDocPtr doc;
|
||||
GList *node;
|
||||
|
||||
doc = xmlNewDoc ("1.0");
|
||||
root_node = xmlNewDocNode (doc, NULL, "location", NULL);
|
||||
|
||||
for (node = backend_list->p->backend_ids; node; node = node->next) {
|
||||
child_node = xmlNewChild (root_node, NULL, "contains", NULL);
|
||||
xmlNewProp (child_node, "backend", node->data);
|
||||
}
|
||||
|
||||
xmlDocSetRootElement (doc, root_node);
|
||||
xmlSaveFile (backend_list->p->filename, doc);
|
||||
xmlFreeDoc (doc);
|
||||
}
|
73
archiver/backend-list.h
Normal file
73
archiver/backend-list.h
Normal file
|
@ -0,0 +1,73 @@
|
|||
/* -*- mode: c; style: linux -*- */
|
||||
|
||||
/* backend-list.h
|
||||
* Copyright (C) 2000 Helix Code, Inc.
|
||||
*
|
||||
* Written by Bradford Hovinen <hovinen@helixcode.com>
|
||||
*
|
||||
* 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, 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., 59 Temple Place - Suite 330, Boston, MA
|
||||
* 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef __BACKEND_LIST_H
|
||||
#define __BACKEND_LIST_H
|
||||
|
||||
#include <gnome.h>
|
||||
|
||||
BEGIN_GNOME_DECLS
|
||||
|
||||
#define BACKEND_LIST(obj) GTK_CHECK_CAST (obj, backend_list_get_type (), BackendList)
|
||||
#define BACKEND_LIST_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, backend_list_get_type (), BackendListClass)
|
||||
#define IS_BACKEND_LIST(obj) GTK_CHECK_TYPE (obj, backend_list_get_type ())
|
||||
|
||||
typedef struct _BackendList BackendList;
|
||||
typedef struct _BackendListClass BackendListClass;
|
||||
typedef struct _BackendListPrivate BackendListPrivate;
|
||||
|
||||
typedef gint (*BackendCB) (BackendList *, gchar *, gpointer);
|
||||
|
||||
struct _BackendList
|
||||
{
|
||||
GtkObject parent;
|
||||
|
||||
BackendListPrivate *p;
|
||||
};
|
||||
|
||||
struct _BackendListClass
|
||||
{
|
||||
GtkObjectClass gtk_object_class;
|
||||
};
|
||||
|
||||
guint backend_list_get_type (void);
|
||||
|
||||
GtkObject *backend_list_new (gboolean is_global);
|
||||
|
||||
gboolean backend_list_contains (BackendList *backend_list,
|
||||
gchar *backend_id);
|
||||
|
||||
gboolean backend_list_foreach (BackendList *backend_list,
|
||||
BackendCB callback,
|
||||
gpointer data);
|
||||
|
||||
void backend_list_add (BackendList *backend_list,
|
||||
gchar *backend_id);
|
||||
void backend_list_remove (BackendList *backend_list,
|
||||
gchar *backend_id);
|
||||
|
||||
void backend_list_save (BackendList *backend_list);
|
||||
|
||||
END_GNOME_DECLS
|
||||
|
||||
#endif /* __BACKEND_LIST_H */
|
933
archiver/config-log.c
Normal file
933
archiver/config-log.c
Normal file
|
@ -0,0 +1,933 @@
|
|||
/* -*- mode: c; style: linux -*- */
|
||||
|
||||
/* config-log.c
|
||||
* Copyright (C) 2000 Helix Code, Inc.
|
||||
*
|
||||
* Written by Bradford Hovinen (hovinen@helixcode.com)
|
||||
*
|
||||
* 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, 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., 59 Temple Place - Suite 330, Boston, MA
|
||||
* 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "config-log.h"
|
||||
#include "location.h"
|
||||
#include "util.h"
|
||||
|
||||
static GtkObjectClass *parent_class;
|
||||
|
||||
enum {
|
||||
ARG_0,
|
||||
ARG_LOCATION
|
||||
};
|
||||
|
||||
typedef struct _ConfigLogEntry ConfigLogEntry;
|
||||
|
||||
struct _ConfigLogEntry
|
||||
{
|
||||
gint id;
|
||||
struct tm *date;
|
||||
gchar *backend_id;
|
||||
};
|
||||
|
||||
static void config_log_init (ConfigLog *config_log);
|
||||
static void config_log_class_init (ConfigLogClass *klass);
|
||||
|
||||
static void config_log_set_arg (GtkObject *object,
|
||||
GtkArg *arg,
|
||||
guint arg_id);
|
||||
|
||||
static void config_log_get_arg (GtkObject *object,
|
||||
GtkArg *arg,
|
||||
guint arg_id);
|
||||
|
||||
static void config_log_destroy (GtkObject *object);
|
||||
|
||||
static GList *find_config_log_entry_id (ConfigLog *config_log,
|
||||
GList *start,
|
||||
gint id);
|
||||
static GList *find_config_log_entry_date (ConfigLog *config_log,
|
||||
GList *start,
|
||||
struct tm *date);
|
||||
static GList *find_config_log_entry_backend (ConfigLog *config_log,
|
||||
GList *start,
|
||||
gchar *backend_id);
|
||||
|
||||
static GList *load_next_log_entry (ConfigLog *config_log,
|
||||
GList *last);
|
||||
|
||||
static gchar *get_line (FILE *file);
|
||||
static gboolean parse_line (char *buffer,
|
||||
int *id,
|
||||
struct tm *time,
|
||||
char **backend_id);
|
||||
static gboolean time_geq (struct tm *time1,
|
||||
struct tm *time2);
|
||||
|
||||
static gboolean do_load (ConfigLog *config_log);
|
||||
static void do_unload (ConfigLog *config_log);
|
||||
|
||||
static gint get_next_id (ConfigLog *config_log);
|
||||
static struct tm *get_current_date (void);
|
||||
static void write_log (FILE *output,
|
||||
ConfigLogEntry *entry);
|
||||
static void dump_log (ConfigLog *config_log);
|
||||
|
||||
static void config_log_entry_destroy (ConfigLogEntry *entry);
|
||||
|
||||
guint
|
||||
config_log_get_type (void)
|
||||
{
|
||||
static guint config_log_type;
|
||||
|
||||
if (!config_log_type) {
|
||||
GtkTypeInfo config_log_info = {
|
||||
"ConfigLog",
|
||||
sizeof (ConfigLog),
|
||||
sizeof (ConfigLogClass),
|
||||
(GtkClassInitFunc) config_log_class_init,
|
||||
(GtkObjectInitFunc) config_log_init,
|
||||
(GtkArgSetFunc) NULL,
|
||||
(GtkArgGetFunc) NULL
|
||||
};
|
||||
|
||||
config_log_type =
|
||||
gtk_type_unique (gtk_object_get_type (),
|
||||
&config_log_info);
|
||||
}
|
||||
|
||||
return config_log_type;
|
||||
}
|
||||
|
||||
static void
|
||||
config_log_init (ConfigLog *config_log)
|
||||
{
|
||||
config_log->location = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
config_log_class_init (ConfigLogClass *klass)
|
||||
{
|
||||
GtkObjectClass *object_class;
|
||||
|
||||
object_class = GTK_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->destroy = config_log_destroy;
|
||||
object_class->set_arg = config_log_set_arg;
|
||||
object_class->get_arg = config_log_get_arg;
|
||||
|
||||
gtk_object_add_arg_type ("ConfigLog::location",
|
||||
GTK_TYPE_POINTER,
|
||||
GTK_ARG_READWRITE,
|
||||
ARG_LOCATION);
|
||||
|
||||
parent_class = gtk_type_class (gtk_object_get_type ());
|
||||
}
|
||||
|
||||
static void
|
||||
config_log_set_arg (GtkObject *object, GtkArg *arg, guint arg_id)
|
||||
{
|
||||
ConfigLog *config_log;
|
||||
|
||||
g_return_if_fail (object != NULL);
|
||||
g_return_if_fail (IS_CONFIG_LOG (object));
|
||||
g_return_if_fail (arg != NULL);
|
||||
|
||||
config_log = CONFIG_LOG (object);
|
||||
|
||||
switch (arg_id) {
|
||||
case ARG_LOCATION:
|
||||
g_return_if_fail (GTK_VALUE_POINTER (*arg) != NULL);
|
||||
g_return_if_fail (IS_LOCATION (GTK_VALUE_POINTER (*arg)));
|
||||
|
||||
config_log->location = GTK_VALUE_POINTER (*arg);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
config_log_get_arg (GtkObject *object, GtkArg *arg, guint arg_id)
|
||||
{
|
||||
ConfigLog *config_log;
|
||||
|
||||
g_return_if_fail (object != NULL);
|
||||
g_return_if_fail (IS_CONFIG_LOG (object));
|
||||
g_return_if_fail (arg != NULL);
|
||||
|
||||
config_log = CONFIG_LOG (object);
|
||||
|
||||
switch (arg_id) {
|
||||
case ARG_LOCATION:
|
||||
GTK_VALUE_POINTER (*arg) = config_log->location;
|
||||
break;
|
||||
default:
|
||||
arg->type = GTK_TYPE_INVALID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Loads a configuration log. Creates it if it has not been created
|
||||
* already
|
||||
*/
|
||||
|
||||
GtkObject *
|
||||
config_log_open (Location *location)
|
||||
{
|
||||
GtkObject *object;
|
||||
|
||||
object = gtk_object_new (config_log_get_type (),
|
||||
"location", location,
|
||||
NULL);
|
||||
|
||||
do_load (CONFIG_LOG (object));
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
/* Destroys a configuration log data structure and frees all memory
|
||||
* associated with it, dumping the existing log out to disk
|
||||
*/
|
||||
|
||||
static void
|
||||
config_log_destroy (GtkObject *object)
|
||||
{
|
||||
ConfigLog *config_log;
|
||||
|
||||
g_return_if_fail (object != NULL);
|
||||
g_return_if_fail (IS_CONFIG_LOG (object));
|
||||
|
||||
config_log = CONFIG_LOG (object);
|
||||
|
||||
do_unload (config_log);
|
||||
|
||||
GTK_OBJECT_CLASS (parent_class)->destroy (GTK_OBJECT (config_log));
|
||||
}
|
||||
|
||||
/**
|
||||
* config_log_delete:
|
||||
* @config_log:
|
||||
*
|
||||
* Permanently destroy a config log, including its log file. Also destory the
|
||||
* object
|
||||
**/
|
||||
|
||||
void
|
||||
config_log_delete (ConfigLog *config_log)
|
||||
{
|
||||
g_return_if_fail (config_log != NULL);
|
||||
g_return_if_fail (IS_CONFIG_LOG (config_log));
|
||||
|
||||
if (config_log->file != NULL) {
|
||||
fclose (config_log->file);
|
||||
config_log->file = NULL;
|
||||
}
|
||||
|
||||
if (config_log->filename != NULL)
|
||||
unlink (config_log->filename);
|
||||
|
||||
gtk_object_destroy (GTK_OBJECT (config_log));
|
||||
}
|
||||
|
||||
/* Return the id number of the most recent data written by the given
|
||||
* backend prior to the given date. If date is NULL, it is assumed to
|
||||
* be today.
|
||||
*/
|
||||
|
||||
gint
|
||||
config_log_get_rollback_id_for_date (ConfigLog *config_log,
|
||||
struct tm *date,
|
||||
gchar *backend_id)
|
||||
{
|
||||
GList *node;
|
||||
|
||||
g_return_val_if_fail (config_log != NULL, -1);
|
||||
g_return_val_if_fail (IS_CONFIG_LOG (config_log), -1);
|
||||
g_return_val_if_fail (backend_id != NULL, -1);
|
||||
|
||||
if (config_log->log_data == NULL)
|
||||
config_log->log_data =
|
||||
load_next_log_entry (config_log, NULL);
|
||||
|
||||
if (date == NULL)
|
||||
node = config_log->log_data;
|
||||
else
|
||||
node = find_config_log_entry_date (config_log,
|
||||
config_log->log_data,
|
||||
date);
|
||||
|
||||
node = find_config_log_entry_backend (config_log, node, backend_id);
|
||||
|
||||
if (!node)
|
||||
return -1;
|
||||
else
|
||||
return ((ConfigLogEntry *) node->data)->id;
|
||||
}
|
||||
|
||||
/* Given a linked list of backend ids and a date, return an array of
|
||||
* ids corresponding to the most recent data written by each of the
|
||||
* backends prior to the given date. The array is in the same order as
|
||||
* the backends and should be freed when done. If date is NULL, it is
|
||||
* assumed to be today.
|
||||
*
|
||||
* FIXME: This should really sort the ids by date.
|
||||
*/
|
||||
|
||||
gint *
|
||||
config_log_get_rollback_ids_for_date (ConfigLog *config_log,
|
||||
struct tm *date,
|
||||
GList *backend_ids)
|
||||
{
|
||||
GList *start_node, *node;
|
||||
gint *id_array, i = 0;
|
||||
|
||||
g_return_val_if_fail (config_log != NULL, NULL);
|
||||
g_return_val_if_fail (IS_CONFIG_LOG (config_log), NULL);
|
||||
g_return_val_if_fail (backend_ids != NULL, NULL);
|
||||
|
||||
if (config_log->log_data == NULL)
|
||||
config_log->log_data =
|
||||
load_next_log_entry (config_log, NULL);
|
||||
|
||||
if (date == NULL)
|
||||
start_node = config_log->log_data;
|
||||
else
|
||||
start_node = find_config_log_entry_date (config_log,
|
||||
config_log->log_data,
|
||||
date);
|
||||
|
||||
id_array = g_new (gint, g_list_length (backend_ids));
|
||||
|
||||
for (; backend_ids; backend_ids = backend_ids->next) {
|
||||
node = find_config_log_entry_backend (config_log,
|
||||
start_node,
|
||||
backend_ids->data);
|
||||
if (!node)
|
||||
id_array[i] = -1;
|
||||
else
|
||||
id_array[i] = ((ConfigLogEntry *) node->data)->id;
|
||||
i++;
|
||||
}
|
||||
|
||||
return id_array;
|
||||
}
|
||||
|
||||
/* Return the rollback id that is the given number of steps back from the
|
||||
* current revision, or -1 if there is no such id
|
||||
*/
|
||||
|
||||
gint
|
||||
config_log_get_rollback_id_by_steps (ConfigLog *config_log,
|
||||
guint steps, gchar *backend_id)
|
||||
{
|
||||
GList *node;
|
||||
|
||||
g_return_val_if_fail (config_log != NULL, -1);
|
||||
g_return_val_if_fail (IS_CONFIG_LOG (config_log), -1);
|
||||
g_return_val_if_fail (backend_id != NULL, -1);
|
||||
|
||||
node = config_log->log_data;
|
||||
|
||||
if (node == NULL)
|
||||
node = load_next_log_entry (config_log, node);
|
||||
|
||||
while (node != NULL && steps-- > 0) {
|
||||
node = find_config_log_entry_backend
|
||||
(config_log, node, backend_id);
|
||||
|
||||
if (steps > 0) {
|
||||
if (node->next == NULL)
|
||||
node = load_next_log_entry (config_log, node);
|
||||
else
|
||||
node = node->next;
|
||||
}
|
||||
}
|
||||
|
||||
if (node != NULL)
|
||||
return ((ConfigLogEntry *) node->data)->id;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Return the backend that generated the data with the given id */
|
||||
|
||||
gchar *
|
||||
config_log_get_backend_id_for_id (ConfigLog *config_log, gint id)
|
||||
{
|
||||
GList *node;
|
||||
|
||||
g_return_val_if_fail (config_log != NULL, NULL);
|
||||
g_return_val_if_fail (IS_CONFIG_LOG (config_log), NULL);
|
||||
g_return_val_if_fail (id >= 0, NULL);
|
||||
|
||||
if (config_log->log_data == NULL)
|
||||
config_log->log_data =
|
||||
load_next_log_entry (config_log, NULL);
|
||||
|
||||
node = find_config_log_entry_id (config_log,
|
||||
config_log->log_data, id);
|
||||
|
||||
if (!node)
|
||||
return NULL;
|
||||
else
|
||||
return ((ConfigLogEntry *) node->data)->backend_id;
|
||||
}
|
||||
|
||||
/* Return the date the data with the given id was written */
|
||||
|
||||
struct tm *
|
||||
config_log_get_date_for_id (ConfigLog *config_log, gint id)
|
||||
{
|
||||
GList *node;
|
||||
|
||||
g_return_val_if_fail (config_log != NULL, NULL);
|
||||
g_return_val_if_fail (IS_CONFIG_LOG (config_log), NULL);
|
||||
g_return_val_if_fail (id >= 0, NULL);
|
||||
|
||||
if (config_log->log_data == NULL)
|
||||
config_log->log_data =
|
||||
load_next_log_entry (config_log, NULL);
|
||||
|
||||
node = find_config_log_entry_id (config_log,
|
||||
config_log->log_data, id);
|
||||
|
||||
if (!node)
|
||||
return NULL;
|
||||
else
|
||||
return ((ConfigLogEntry *) node->data)->date;
|
||||
}
|
||||
|
||||
gint
|
||||
config_log_write_entry (ConfigLog *config_log, gchar *backend_id)
|
||||
{
|
||||
ConfigLogEntry *entry;
|
||||
|
||||
g_return_val_if_fail (config_log != NULL, -1);
|
||||
g_return_val_if_fail (IS_CONFIG_LOG (config_log), -1);
|
||||
g_return_val_if_fail (backend_id != NULL, -1);
|
||||
|
||||
entry = g_new0 (ConfigLogEntry, 1);
|
||||
entry->id = get_next_id (config_log);
|
||||
entry->date = get_current_date ();
|
||||
entry->backend_id = g_strdup (backend_id);
|
||||
|
||||
config_log->log_data = g_list_prepend (config_log->log_data, entry);
|
||||
|
||||
return entry->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* config_log_iterate:
|
||||
* @config_log:
|
||||
* @callback:
|
||||
* @data:
|
||||
*
|
||||
* Iterate through all log entries an invoke the given callback on each one,
|
||||
* passing the id, date created, and backend id to it
|
||||
**/
|
||||
|
||||
void
|
||||
config_log_iterate (ConfigLog *config_log, ConfigLogIteratorCB callback,
|
||||
gpointer data)
|
||||
{
|
||||
GList *node;
|
||||
ConfigLogEntry *entry;
|
||||
|
||||
g_return_if_fail (config_log != NULL);
|
||||
g_return_if_fail (IS_CONFIG_LOG (config_log));
|
||||
g_return_if_fail (callback != NULL);
|
||||
|
||||
node = config_log->log_data;
|
||||
while (node != NULL) {
|
||||
entry = (ConfigLogEntry *) node->data;
|
||||
if (callback (config_log, entry->id, entry->backend_id,
|
||||
entry->date, node->data)) break;
|
||||
|
||||
if (node->next == NULL)
|
||||
node = load_next_log_entry (config_log, node);
|
||||
else
|
||||
node = node->next;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* config_log_reset_filenames:
|
||||
* @config_log:
|
||||
*
|
||||
* Rereads the log's location data to determine filenames
|
||||
**/
|
||||
|
||||
void
|
||||
config_log_reset_filenames (ConfigLog *config_log)
|
||||
{
|
||||
g_return_if_fail (config_log != NULL);
|
||||
g_return_if_fail (IS_CONFIG_LOG (config_log));
|
||||
|
||||
if (config_log->filename != NULL)
|
||||
g_free (config_log->filename);
|
||||
|
||||
config_log->filename =
|
||||
g_concat_dir_and_file (location_get_path
|
||||
(config_log->location),
|
||||
"config.log");
|
||||
if (config_log->lock_filename != NULL)
|
||||
g_free (config_log->lock_filename);
|
||||
|
||||
config_log->lock_filename =
|
||||
g_concat_dir_and_file (location_get_path
|
||||
(config_log->location),
|
||||
"config.log.lock");
|
||||
}
|
||||
|
||||
/* Find the config log entry with the id given, starting at the given
|
||||
* node. Return a pointer to the node.
|
||||
*/
|
||||
|
||||
static GList *
|
||||
find_config_log_entry_id (ConfigLog *config_log, GList *start, gint id)
|
||||
{
|
||||
GList *last;
|
||||
ConfigLogEntry *entry;
|
||||
|
||||
g_return_val_if_fail (config_log != NULL, NULL);
|
||||
g_return_val_if_fail (IS_CONFIG_LOG (config_log), NULL);
|
||||
g_return_val_if_fail (id >= 0, NULL);
|
||||
|
||||
if (!start) return NULL;
|
||||
|
||||
while (start != NULL) {
|
||||
last = start;
|
||||
entry = (ConfigLogEntry *) start->data;
|
||||
if (entry->id == id)
|
||||
return start;
|
||||
else if (entry->id < id)
|
||||
return NULL;
|
||||
start = start->next;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
start = load_next_log_entry (config_log, last);
|
||||
if (start == NULL) return NULL;
|
||||
entry = (ConfigLogEntry *) start->data;
|
||||
if (entry->id == id)
|
||||
return start;
|
||||
else if (entry->id < id)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Find the first config log entry made prior to the given date,
|
||||
* starting at the given node. Return a pointer to the node.
|
||||
*/
|
||||
|
||||
static GList *
|
||||
find_config_log_entry_date (ConfigLog *config_log, GList *start,
|
||||
struct tm *date)
|
||||
{
|
||||
GList *last;
|
||||
ConfigLogEntry *entry;
|
||||
|
||||
g_return_val_if_fail (config_log != NULL, NULL);
|
||||
g_return_val_if_fail (IS_CONFIG_LOG (config_log), NULL);
|
||||
g_return_val_if_fail (date != NULL, NULL);
|
||||
|
||||
if (!start) return NULL;
|
||||
|
||||
while (start != NULL) {
|
||||
last = start;
|
||||
entry = (ConfigLogEntry *) start->data;
|
||||
if (time_geq (date, entry->date))
|
||||
return start;
|
||||
start = start->next;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
start = load_next_log_entry (config_log, last);
|
||||
if (start == NULL) return NULL;
|
||||
entry = (ConfigLogEntry *) start->data;
|
||||
if (time_geq (date, entry->date))
|
||||
return start;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Find the first config log entry made by the given backend,
|
||||
* starting at the given node. Return a pointer to the node.
|
||||
*/
|
||||
|
||||
static GList *
|
||||
find_config_log_entry_backend (ConfigLog *config_log, GList *start,
|
||||
gchar *backend_id)
|
||||
{
|
||||
GList *last;
|
||||
ConfigLogEntry *entry;
|
||||
|
||||
g_return_val_if_fail (config_log != NULL, NULL);
|
||||
g_return_val_if_fail (IS_CONFIG_LOG (config_log), NULL);
|
||||
g_return_val_if_fail (backend_id != NULL, NULL);
|
||||
|
||||
if (!start) return NULL;
|
||||
|
||||
while (start != NULL) {
|
||||
last = start;
|
||||
entry = (ConfigLogEntry *) start->data;
|
||||
if (!strcmp (entry->backend_id, backend_id))
|
||||
return start;
|
||||
start = start->next;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
start = load_next_log_entry (config_log, last);
|
||||
if (start == NULL) return NULL;
|
||||
entry = (ConfigLogEntry *) start->data;
|
||||
if (!strcmp (entry->backend_id, backend_id))
|
||||
return start;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static GList *
|
||||
load_next_log_entry (ConfigLog *config_log, GList *last)
|
||||
{
|
||||
gchar *buffer, *backend_id;
|
||||
ConfigLogEntry *entry;
|
||||
gboolean success;
|
||||
|
||||
g_return_val_if_fail (config_log != NULL, NULL);
|
||||
g_return_val_if_fail (IS_CONFIG_LOG (config_log), NULL);
|
||||
|
||||
if (!config_log->file || feof (config_log->file)) return NULL;
|
||||
|
||||
buffer = get_line (config_log->file);
|
||||
|
||||
entry = g_new0 (ConfigLogEntry, 1);
|
||||
entry->date = g_new0 (struct tm, 1);
|
||||
success = parse_line (buffer, &entry->id,
|
||||
entry->date, &backend_id);
|
||||
|
||||
if (success) {
|
||||
entry->backend_id = g_strdup (backend_id);
|
||||
last = g_list_append (last, entry);
|
||||
|
||||
if (!config_log->log_data) {
|
||||
config_log->log_data = last;
|
||||
config_log->first_old = last;
|
||||
}
|
||||
|
||||
return g_list_find (last, entry);
|
||||
} else {
|
||||
g_free (entry);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Read an entire line from the given file, returning a pointer to an
|
||||
* allocated string. Strip the trailing newline from the line.
|
||||
*/
|
||||
|
||||
static gchar *
|
||||
get_line (FILE *file)
|
||||
{
|
||||
int buf_size = 0;
|
||||
char *buf = NULL, *tmp = NULL;
|
||||
size_t distance = 0, amt_read = 0;
|
||||
|
||||
g_return_val_if_fail (file != NULL, NULL);
|
||||
|
||||
while (amt_read == buf_size - distance) {
|
||||
distance = tmp - buf;
|
||||
|
||||
if (distance >= buf_size) {
|
||||
if (buf == NULL) {
|
||||
buf_size = 1024;
|
||||
buf = g_new (char, buf_size);
|
||||
} else {
|
||||
buf_size *= 2;
|
||||
buf = g_renew (char, buf, buf_size);
|
||||
}
|
||||
|
||||
tmp = buf + distance;
|
||||
}
|
||||
|
||||
fgets (tmp, buf_size - distance, file);
|
||||
amt_read = strlen (tmp);
|
||||
tmp += amt_read;
|
||||
}
|
||||
|
||||
if (tmp) *(tmp - 1) = '\0';
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* Parse a line from the log file. All pointers must be valid.
|
||||
*
|
||||
* Note: backend just points to somewhere in buffer, so it becomes
|
||||
* invalid the next time the buffer is overwritten. If there's a
|
||||
* trailing newline, it is not chopped off.
|
||||
*
|
||||
* Returns TRUE on success and FALSE on parse error; if FALSE is
|
||||
* returned, the values placed in the variables given are undefined.
|
||||
*/
|
||||
|
||||
static gboolean
|
||||
parse_line (char *buffer, int *id, struct tm *date, char **backend_id)
|
||||
{
|
||||
sscanf (buffer, "%x", id);
|
||||
|
||||
while (isxdigit (*buffer)) buffer++;
|
||||
|
||||
if (!isspace (*buffer) || !isdigit (*(buffer + 1))) return FALSE;
|
||||
buffer++;
|
||||
|
||||
if (extract_number (&buffer, &date->tm_year, 4) == FALSE)
|
||||
return FALSE;
|
||||
if (extract_number (&buffer, &date->tm_mon, 2) == FALSE)
|
||||
return FALSE;
|
||||
if (extract_number (&buffer, &date->tm_mday, 2) == FALSE)
|
||||
return FALSE;
|
||||
|
||||
date->tm_year -= 1900;
|
||||
date->tm_mon--;
|
||||
|
||||
if (!isspace (*buffer) || !isdigit (*(buffer + 1))) return FALSE;
|
||||
buffer++;
|
||||
|
||||
if (extract_number (&buffer, &date->tm_hour, 2) == FALSE)
|
||||
return FALSE;
|
||||
if (*buffer != ':') return FALSE; buffer++;
|
||||
if (extract_number (&buffer, &date->tm_min, 2) == FALSE)
|
||||
return FALSE;
|
||||
if (*buffer != ':') return FALSE; buffer++;
|
||||
if (extract_number (&buffer, &date->tm_sec, 2) == FALSE)
|
||||
return FALSE;
|
||||
|
||||
if (!isspace (*buffer) || *(buffer + 1) == '\0') return FALSE;
|
||||
buffer++;
|
||||
|
||||
*backend_id = buffer;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Return TRUE if the first given struct tm is greater than or equal
|
||||
* to the second given struct tm; FALSE otherwise
|
||||
*/
|
||||
|
||||
static gboolean
|
||||
time_geq (struct tm *time1, struct tm *time2)
|
||||
{
|
||||
if (time1->tm_year > time2->tm_year) return TRUE;
|
||||
if (time1->tm_year < time2->tm_year) return FALSE;
|
||||
if (time1->tm_mon > time2->tm_mon) return TRUE;
|
||||
if (time1->tm_mon < time2->tm_mon) return FALSE;
|
||||
if (time1->tm_mday > time2->tm_mday) return TRUE;
|
||||
if (time1->tm_mday < time2->tm_mday) return FALSE;
|
||||
if (time1->tm_hour > time2->tm_hour) return TRUE;
|
||||
if (time1->tm_hour < time2->tm_hour) return FALSE;
|
||||
if (time1->tm_min > time2->tm_min) return TRUE;
|
||||
if (time1->tm_min < time2->tm_min) return FALSE;
|
||||
if (time1->tm_sec >= time2->tm_sec) return TRUE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Opens up a configuration log. Assumes all the structures are
|
||||
* already initialized. Creates the log if not already done.
|
||||
*
|
||||
* Returns TRUE on success and FALSE on failure (unable to open output
|
||||
* file or log is locked)
|
||||
*/
|
||||
|
||||
static gboolean
|
||||
do_load (ConfigLog *config_log)
|
||||
{
|
||||
FILE *lock_file;
|
||||
|
||||
g_return_val_if_fail (config_log != NULL, FALSE);
|
||||
g_return_val_if_fail (IS_CONFIG_LOG (config_log), FALSE);
|
||||
g_return_val_if_fail (config_log->location != NULL, FALSE);
|
||||
g_return_val_if_fail (IS_LOCATION (config_log->location), FALSE);
|
||||
|
||||
do_unload (config_log);
|
||||
config_log_reset_filenames (config_log);
|
||||
|
||||
/* FIXME: Race condition here, plus lock handling should be
|
||||
* better */
|
||||
|
||||
if (g_file_test (config_log->lock_filename, G_FILE_TEST_ISFILE))
|
||||
return FALSE;
|
||||
|
||||
lock_file = fopen (config_log->lock_filename, "w");
|
||||
fclose (lock_file);
|
||||
|
||||
config_log->file = fopen (config_log->filename, "r");
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Closes the input file for a given log and dumps and clears the
|
||||
* cache
|
||||
*/
|
||||
|
||||
static void
|
||||
do_unload (ConfigLog *config_log)
|
||||
{
|
||||
GList *tmp;
|
||||
|
||||
g_return_if_fail (config_log != NULL);
|
||||
g_return_if_fail (IS_CONFIG_LOG (config_log));
|
||||
|
||||
dump_log (config_log);
|
||||
|
||||
if (config_log->file) {
|
||||
fclose (config_log->file);
|
||||
config_log->file = NULL;
|
||||
}
|
||||
|
||||
if (config_log->filename) {
|
||||
g_free (config_log->filename);
|
||||
config_log->filename = NULL;
|
||||
}
|
||||
|
||||
if (config_log->lock_filename) {
|
||||
unlink (config_log->lock_filename);
|
||||
g_free (config_log->lock_filename);
|
||||
config_log->lock_filename = NULL;
|
||||
}
|
||||
|
||||
while (config_log->log_data) {
|
||||
tmp = config_log->log_data->next;
|
||||
config_log_entry_destroy
|
||||
((ConfigLogEntry *) config_log->log_data->data);
|
||||
g_list_free_1 (config_log->log_data);
|
||||
config_log->log_data = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns the next id number in the sequence */
|
||||
|
||||
static gint
|
||||
get_next_id (ConfigLog *config_log)
|
||||
{
|
||||
if (config_log->log_data == NULL) {
|
||||
if (load_next_log_entry (config_log, NULL) == NULL)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ((ConfigLogEntry *) config_log->log_data->data)->id + 1;
|
||||
}
|
||||
|
||||
/* Return a newly allocated struct tm with the current time */
|
||||
|
||||
static struct tm *
|
||||
get_current_date (void)
|
||||
{
|
||||
time_t current_time;
|
||||
struct tm *time_1, *ret;
|
||||
|
||||
current_time = time (NULL);
|
||||
time_1 = gmtime (¤t_time);
|
||||
ret = g_new (struct tm, 1);
|
||||
memcpy (ret, time_1, sizeof (struct tm));
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Write out a log entry */
|
||||
|
||||
static void
|
||||
write_log (FILE *output, ConfigLogEntry *entry)
|
||||
{
|
||||
g_return_if_fail (output != NULL);
|
||||
g_return_if_fail (entry != NULL);
|
||||
g_return_if_fail (entry->id >= 0);
|
||||
g_return_if_fail (entry->date != NULL);
|
||||
g_return_if_fail (entry->backend_id != NULL);
|
||||
|
||||
fprintf (output, "%08x %04d%02d%02d %02d:%02d:%02d %s\n",
|
||||
entry->id, entry->date->tm_year + 1900,
|
||||
entry->date->tm_mon + 1, entry->date->tm_mday,
|
||||
entry->date->tm_hour, entry->date->tm_min,
|
||||
entry->date->tm_sec, entry->backend_id);
|
||||
}
|
||||
|
||||
static void
|
||||
dump_log (ConfigLog *config_log)
|
||||
{
|
||||
char *filename_out;
|
||||
FILE *output;
|
||||
GList *first;
|
||||
char buffer[16384];
|
||||
size_t size;
|
||||
|
||||
g_return_if_fail (config_log != NULL);
|
||||
g_return_if_fail (IS_CONFIG_LOG (config_log));
|
||||
g_return_if_fail (config_log->location != NULL);
|
||||
g_return_if_fail (IS_LOCATION (config_log->location));
|
||||
g_return_if_fail (location_get_path (config_log->location) != NULL);
|
||||
|
||||
filename_out = g_concat_dir_and_file (location_get_path
|
||||
(config_log->location),
|
||||
"config.log.out");
|
||||
|
||||
output = fopen (filename_out, "w");
|
||||
|
||||
if (!output) {
|
||||
g_warning ("Could not open output file: %s",
|
||||
g_strerror (errno));
|
||||
return;
|
||||
}
|
||||
|
||||
for (first = config_log->log_data; first != config_log->first_old;
|
||||
first = first->next)
|
||||
write_log (output, first->data);
|
||||
|
||||
if (config_log->file) {
|
||||
rewind (config_log->file);
|
||||
|
||||
while (!feof (config_log->file)) {
|
||||
size = fread (buffer, sizeof (char),
|
||||
16384, config_log->file);
|
||||
fwrite (buffer, sizeof (char), size, output);
|
||||
}
|
||||
}
|
||||
|
||||
fclose (output);
|
||||
|
||||
if (config_log->filename)
|
||||
rename (filename_out, config_log->filename);
|
||||
}
|
||||
|
||||
static void
|
||||
config_log_entry_destroy (ConfigLogEntry *entry)
|
||||
{
|
||||
g_return_if_fail (entry != NULL);
|
||||
g_return_if_fail (entry->date != NULL);
|
||||
g_return_if_fail (entry->backend_id != NULL);
|
||||
|
||||
g_free (entry->date);
|
||||
g_free (entry->backend_id);
|
||||
g_free (entry);
|
||||
}
|
92
archiver/config-log.h
Normal file
92
archiver/config-log.h
Normal file
|
@ -0,0 +1,92 @@
|
|||
/* -*- mode: c; style: linux -*- */
|
||||
|
||||
/* config-log.h
|
||||
* Copyright (C) 2000 Helix Code, Inc.
|
||||
*
|
||||
* Written by Bradford Hovinen (hovinen@helixcode.com)
|
||||
*
|
||||
* 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, 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., 59 Temple Place - Suite 330, Boston, MA
|
||||
* 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef __CONFIG_LOG_H
|
||||
#define __CONFIG_LOG_H
|
||||
|
||||
#include <gnome.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
#define CONFIG_LOG(obj) GTK_CHECK_CAST (obj, config_log_get_type (), ConfigLog)
|
||||
#define CONFIG_LOG_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, config_log_get_type (), ConfigLogClass)
|
||||
#define IS_CONFIG_LOG(obj) GTK_CHECK_TYPE (obj, config_log_get_type ())
|
||||
|
||||
typedef struct _ConfigLog ConfigLog;
|
||||
typedef struct _ConfigLogClass ConfigLogClass;
|
||||
|
||||
typedef struct _Location Location;
|
||||
|
||||
typedef gint (*ConfigLogIteratorCB) (ConfigLog *, gint, gchar *,
|
||||
struct tm *, gpointer);
|
||||
|
||||
struct _ConfigLog
|
||||
{
|
||||
GtkObject object;
|
||||
|
||||
Location *location;
|
||||
|
||||
FILE *file;
|
||||
char *filename;
|
||||
char *lock_filename;
|
||||
|
||||
GList *log_data;
|
||||
GList *first_old;
|
||||
};
|
||||
|
||||
struct _ConfigLogClass
|
||||
{
|
||||
GtkObjectClass parent;
|
||||
};
|
||||
|
||||
guint config_log_get_type (void);
|
||||
|
||||
GtkObject *config_log_open (Location *location);
|
||||
void config_log_delete (ConfigLog *config_log);
|
||||
|
||||
gint config_log_get_rollback_id_for_date (ConfigLog *config_log,
|
||||
struct tm *date,
|
||||
gchar *backend_id);
|
||||
gint *config_log_get_rollback_ids_for_date (ConfigLog *config_log,
|
||||
struct tm *date,
|
||||
GList *backend_ids);
|
||||
|
||||
gint config_log_get_rollback_id_by_steps (ConfigLog *config_log,
|
||||
guint steps,
|
||||
gchar *backend_id);
|
||||
|
||||
gchar *config_log_get_backend_id_for_id (ConfigLog *config_log,
|
||||
gint id);
|
||||
struct tm *config_log_get_date_for_id (ConfigLog *config_log,
|
||||
gint id);
|
||||
|
||||
gint config_log_write_entry (ConfigLog *config_log,
|
||||
gchar *backend_id);
|
||||
|
||||
void config_log_iterate (ConfigLog *config_log,
|
||||
ConfigLogIteratorCB callback,
|
||||
gpointer data);
|
||||
|
||||
void config_log_reset_filenames (ConfigLog *config_log);
|
||||
|
||||
#endif /* __CONFIG_LOG */
|
95
archiver/future-spec
Normal file
95
archiver/future-spec
Normal file
|
@ -0,0 +1,95 @@
|
|||
Changes to the Helix Configuration Manager
|
||||
Copyright (C) 2000 Helix Code, Inc.
|
||||
Written by Bradford Hovinen <hovinen@helixcode.com>
|
||||
|
||||
As it stands, capplets and Helix Setup Tools are both run as separate
|
||||
processes through the exec() facility. It is planned that the capplets
|
||||
shall become Bonobo controls in the future, once the OAF/gnorba
|
||||
compatibility problems are worked out. This changes the design of the
|
||||
configuration system considerably, and several things should be done
|
||||
to take full advantage of these changes.
|
||||
|
||||
1. Capplets become Bonobo controls
|
||||
|
||||
It stands to reason that the front ends for Helix Setup Tools should
|
||||
become Bonobo controls at the same time as capplets. They can each
|
||||
implement the same interface (say, Bonobo::Capplet) with methods
|
||||
getXML(), setXML(), ok(), cancel(), and init() and hence look the same
|
||||
to the shell. This means that the front ends for the Helix Setup Tools
|
||||
run as the same user as HCM and respond in the same way as capplets do
|
||||
to requests to embed them in the HCM shell window. This is essential
|
||||
for a consistent user interface that will not result in end-user
|
||||
confusion [1]. HCM itself may then export an interface that includes
|
||||
the method runBackend(), to which the frontend supplies a stream of
|
||||
XML that HCM passes through the root manager to the backend via a
|
||||
standard pipe [2]. The backend is then responsible for running the
|
||||
program that archives the XML -- there is no other way to place that
|
||||
XML in a central, system-wide repository. I suggest, therefore, that
|
||||
we modify the design of the current system to make that change, so
|
||||
that we do not have to undo existing work later.
|
||||
|
||||
2. Backends get CORBA interfaces through Perl/ORBit
|
||||
|
||||
At this point, there must be a way for the root manager to forward
|
||||
CORBA sockets to the user securely. This could be done by modifying
|
||||
ORBit so as to give the running program very precise control over the
|
||||
nature of the socket. Access could be granted specifically to the user
|
||||
running the root manager by placing the socket in a directory owned by
|
||||
that user with permissions no more lax than 0700. When the CORBA
|
||||
interfaces are created, applications will be able to make use of it to
|
||||
make system-wide changes as necessary (say, to add a new user during
|
||||
the installation of a piece of software). This means that the
|
||||
traditional rollback facilities must be extended to allow users to
|
||||
roll back changes made by applications. In addition, the application
|
||||
must treat the backend as a black box -- it should never be expected
|
||||
to do anything unusual to support rollback, since buggy or
|
||||
poorly-written applications would otherwise cause trouble for
|
||||
unsuspecting users.
|
||||
|
||||
At this point I suggest that each backend export two interfaces: one
|
||||
that is universal to all backends and one that is specific to that
|
||||
particular tool. The former may include the methods getXML(),
|
||||
setXML(), and commit(). When changes are made through the
|
||||
tool-specific interface, the tool decides whether or not to apply
|
||||
those changes immediately or to queue them up until a commit() is
|
||||
invoked. If changes are made through the backend's CORBA interface and
|
||||
it is deactivated before a commit(), the backend must roll back those
|
||||
changes under the assumption that they are not intended to be
|
||||
permanent.
|
||||
|
||||
Of course, this makes implementation of the cancel() interface on the
|
||||
frontends very easy -- simply deactivate the backend without
|
||||
commit()ing. ok() can be implemented by flushing any remaining
|
||||
changes, calling commit(), and then deactivating the backend. The
|
||||
frontend can and should use the CORBA interface to invoke changes
|
||||
whenever they are made, as long as it makes sense. It is then the
|
||||
backend that sets the policy of whether or not the updates are live,
|
||||
as described above. The frontend must still be able to read XML,
|
||||
though, since it is through that that it will get an initial
|
||||
description of the setup with which to fill in the dialog. In
|
||||
addition, since the frontend may be invoked to make changes to an
|
||||
inactive location, it should be able to write out an XML description
|
||||
of the dialog's contents so that those changes may be archived rather
|
||||
than applied immediately.
|
||||
|
||||
Notes
|
||||
|
||||
[1] A visual cue that signals to the user that he is running a
|
||||
system-wide configuration tool rather than a personal one would be
|
||||
advantageous. Such could take the form of an icon on the dialog, a
|
||||
layout or formatting convention for the dialog proper, or some sort of
|
||||
coloring convention of some of the controls. However, simply having
|
||||
the tool run with root's Gtk+ theme and ignoring the embedding
|
||||
preference, as would be the case if we do not Bonobize the HST
|
||||
frontends, is inconsistent as many users will leave their themes as
|
||||
the default and elect not to embed capplets -- eliminating all visual
|
||||
cues. In addition, it is not particularly lucid and many users will
|
||||
merely be confused by the inconsistent interface. One may imagine many
|
||||
users filing bug reports in the belief that the behavior is
|
||||
erroneous. Hence, that solution is insufficient.
|
||||
|
||||
[2] There must then be a method of multiplexing I/O throught the root
|
||||
manager, as there may be multiple backends running concurrently. A
|
||||
simple protocol could be implemented to do this, or a named pipe could
|
||||
be created if done very carefully as to ensure a high degree of
|
||||
security.
|
1232
archiver/location.c
Normal file
1232
archiver/location.c
Normal file
File diff suppressed because it is too large
Load diff
112
archiver/location.h
Normal file
112
archiver/location.h
Normal file
|
@ -0,0 +1,112 @@
|
|||
/* -*- mode: c; style: linux -*- */
|
||||
|
||||
/* location.h
|
||||
* Copyright (C) 2000 Helix Code, Inc.
|
||||
*
|
||||
* Written by Bradford Hovinen (hovinen@helixcode.com)
|
||||
*
|
||||
* 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, 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., 59 Temple Place - Suite 330, Boston, MA
|
||||
* 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef __LOCATION_H
|
||||
#define __LOCATION_H
|
||||
|
||||
#include <gnome.h>
|
||||
|
||||
#include "config-log.h"
|
||||
|
||||
#define LOCATION(obj) GTK_CHECK_CAST (obj, location_get_type (), Location)
|
||||
#define LOCATION_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, location_get_type (), LocationClass)
|
||||
#define IS_LOCATION(obj) GTK_CHECK_TYPE (obj, location_get_type ())
|
||||
|
||||
typedef struct _LocationClass LocationClass;
|
||||
typedef struct _LocationPrivate LocationPrivate;
|
||||
typedef struct _Archive Archive;
|
||||
|
||||
typedef int (*LocationBackendCB) (Location *, gchar *, gpointer);
|
||||
|
||||
struct _Location
|
||||
{
|
||||
GtkObject object;
|
||||
|
||||
LocationPrivate *p;
|
||||
};
|
||||
|
||||
struct _LocationClass
|
||||
{
|
||||
GtkObjectClass parent;
|
||||
};
|
||||
|
||||
guint location_get_type (void);
|
||||
|
||||
GtkObject *location_new (Archive *archive,
|
||||
const gchar *locid,
|
||||
Location *inherits);
|
||||
GtkObject *location_open (Archive *archive,
|
||||
const gchar *locid);
|
||||
|
||||
void location_close (Location *location);
|
||||
void location_delete (Location *location);
|
||||
|
||||
void location_store (Location *location,
|
||||
gchar *backend_id,
|
||||
FILE *input);
|
||||
|
||||
void location_rollback_backend_to (Location *location,
|
||||
struct tm *date,
|
||||
gchar *backend_id,
|
||||
gboolean parent_chain);
|
||||
void location_rollback_backends_to (Location *location,
|
||||
struct tm *date,
|
||||
GList *backends,
|
||||
gboolean parent_chain);
|
||||
void location_rollback_all_to (Location *location,
|
||||
struct tm *date,
|
||||
gboolean parent_chain);
|
||||
|
||||
void location_rollback_backend_by (Location *location,
|
||||
guint steps,
|
||||
gchar *backend_id,
|
||||
gboolean parent_chain);
|
||||
|
||||
void location_rollback_id (Location *location,
|
||||
gint id);
|
||||
|
||||
void location_dump_rollback_data (Location *location,
|
||||
struct tm *date,
|
||||
guint steps,
|
||||
gchar *backend_id,
|
||||
gboolean parent_chain,
|
||||
FILE *output);
|
||||
|
||||
gboolean location_contains (Location *location, gchar *backend_id);
|
||||
gint location_add_backend (Location *location, gchar *backend_id);
|
||||
void location_remove_backend (Location *location, gchar *backend_id);
|
||||
|
||||
void location_foreach_backend (Location *location,
|
||||
LocationBackendCB callback,
|
||||
gpointer data);
|
||||
|
||||
GList *location_find_path_from_common_parent (Location *location,
|
||||
Location *location2);
|
||||
|
||||
const gchar *location_get_path (Location *location);
|
||||
const gchar *location_get_label (Location *location);
|
||||
const gchar *location_get_id (Location *location);
|
||||
|
||||
void location_set_id (Location *location, const gchar *locid);
|
||||
|
||||
#endif /* __LOCATION */
|
316
archiver/main.c
Normal file
316
archiver/main.c
Normal file
|
@ -0,0 +1,316 @@
|
|||
/* -*- mode: c; style: linux -*- */
|
||||
|
||||
/* main.c
|
||||
* Copyright (C) 2000 Helix Code, Inc.
|
||||
*
|
||||
* Written by Bradford Hovinen (hovinen@helixcode.com)
|
||||
*
|
||||
* 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, 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., 59 Temple Place - Suite 330, Boston, MA
|
||||
* 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/file.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <gnome.h>
|
||||
|
||||
#include "util.h"
|
||||
#include "archive.h"
|
||||
|
||||
/* Variables resulting from command line parsing */
|
||||
|
||||
static gboolean store;
|
||||
static gboolean rollback;
|
||||
static gboolean change_location;
|
||||
static gboolean rename_location;
|
||||
static gboolean push_config;
|
||||
|
||||
static gboolean add_location;
|
||||
static gboolean remove_location;
|
||||
static gboolean add_backend;
|
||||
static gboolean remove_backend;
|
||||
|
||||
static gboolean global;
|
||||
static const gchar *location_id;
|
||||
|
||||
static gchar *backend_id;
|
||||
|
||||
static gchar *date_str;
|
||||
static gboolean all;
|
||||
static gchar *revision_id;
|
||||
static gboolean last;
|
||||
static guint steps;
|
||||
static gboolean show;
|
||||
|
||||
static gchar *parent_str;
|
||||
static gchar *new_name;
|
||||
|
||||
static gboolean master;
|
||||
|
||||
static struct poptOption archiver_operations[] = {
|
||||
{"store", 's', POPT_ARG_NONE, &store, 0,
|
||||
N_("Store XML data in the archive")},
|
||||
{"rollback", 'r', POPT_ARG_NONE, &rollback, 0,
|
||||
N_("Roll back the configuration to a given point")},
|
||||
{"change-location", 'c', POPT_ARG_NONE, &change_location, 0,
|
||||
N_("Change the location profile to the given one")},
|
||||
{"push-config", 'p', POPT_ARG_NONE, &push_config, 0,
|
||||
N_("Push configuration data out to client machines (UNIMPLEMENTED)")},
|
||||
{"rename-location", '\0', POPT_ARG_NONE, &rename_location, 0,
|
||||
N_("Rename a location to a new name")},
|
||||
{"add-location", '\0', POPT_ARG_NONE, &add_location, 0,
|
||||
N_("Add a new location to the archive")},
|
||||
{"remove-location", '\0', POPT_ARG_NONE, &remove_location, 0,
|
||||
N_("Remove a location from the archive")},
|
||||
{"add-backend", '\0', POPT_ARG_NONE, &add_backend, 0,
|
||||
N_("Add a given backend to the given location")},
|
||||
{"remove-backend", '\0', POPT_ARG_NONE, &remove_backend, 0,
|
||||
N_("Remove the given backend from the given location")},
|
||||
{NULL, '\0', 0, NULL, 0}
|
||||
};
|
||||
|
||||
static struct poptOption global_options[] = {
|
||||
{"global", 'g', POPT_ARG_NONE, &global, 0,
|
||||
N_("Use the global repository")},
|
||||
{"location", 'l', POPT_ARG_STRING, &location_id, 0,
|
||||
N_("Identifier of location profile on which to operate"),
|
||||
N_("LOCATION")},
|
||||
{"backend", 'b', POPT_ARG_STRING, &backend_id, 0,
|
||||
N_("Backend being used for this operation"), N_("BACKEND_ID")},
|
||||
{NULL, '\0', 0, NULL, 0}
|
||||
};
|
||||
|
||||
static struct poptOption rollback_options[] = {
|
||||
{"date", 'd', POPT_ARG_STRING, &date_str, 0,
|
||||
N_("Date to which to roll back"), N_("DATE")},
|
||||
{"all", 'a', POPT_ARG_NONE, &all, 0,
|
||||
N_("Roll back all configuration items")},
|
||||
{"revision-id", 'i', POPT_ARG_INT, &revision_id, 0,
|
||||
N_("Roll back to the revision REVISION_ID"), N_("REVISION_ID")},
|
||||
{"last", 't', POPT_ARG_NONE, &last, 0,
|
||||
N_("Roll back to the last known revision")},
|
||||
{"steps", '\0', POPT_ARG_INT, &steps, 0,
|
||||
N_("Roll back by STEPS revisions"), N_("STEPS")},
|
||||
{"show", 'h', POPT_ARG_NONE, &show, 0,
|
||||
N_("Don't run the backend, just dump the output")},
|
||||
{NULL, '\0', 0, NULL, 0}
|
||||
};
|
||||
|
||||
static struct poptOption add_rename_location_options[] = {
|
||||
{"parent", '\0', POPT_ARG_STRING, &parent_str, 0,
|
||||
N_("Parent location for the new location"), N_("PARENT")},
|
||||
{"new-name", '\0', POPT_ARG_STRING, &new_name, 0,
|
||||
N_("New name to assign to the location"), N_("NEW_NAME")},
|
||||
{NULL, '\0', 0, NULL, 0}
|
||||
};
|
||||
|
||||
static struct poptOption add_remove_backend_options[] = {
|
||||
{"master", '\0', POPT_ARG_NONE, &master, 0,
|
||||
N_("Add/remove this backend to/from the master backend list")},
|
||||
{NULL, '\0', 0, NULL, 0}
|
||||
};
|
||||
|
||||
static void
|
||||
do_store (Location *location)
|
||||
{
|
||||
if (!backend_id) {
|
||||
g_message ("No backend specified");
|
||||
return;
|
||||
}
|
||||
|
||||
location_store (location, backend_id, stdin);
|
||||
}
|
||||
|
||||
static void
|
||||
do_rollback (Location *location)
|
||||
{
|
||||
gint id;
|
||||
struct tm *date = NULL;
|
||||
|
||||
if (date_str)
|
||||
date = parse_date (date_str);
|
||||
else if (last || steps > 0)
|
||||
date = NULL;
|
||||
else if (!revision_id) {
|
||||
g_message ("No date specified");
|
||||
return;
|
||||
}
|
||||
|
||||
if (all) {
|
||||
location_rollback_all_to (location, date, TRUE);
|
||||
}
|
||||
else if (backend_id && (date || last)) {
|
||||
/* FIXME: Need to support specifying multiple backends */
|
||||
if (show)
|
||||
location_dump_rollback_data (location, date, 0,
|
||||
backend_id, TRUE, stdout);
|
||||
else
|
||||
location_rollback_backend_to (location, date,
|
||||
backend_id, TRUE);
|
||||
}
|
||||
else if (backend_id && steps) {
|
||||
if (show)
|
||||
location_dump_rollback_data (location, NULL, steps,
|
||||
backend_id, TRUE, stdout);
|
||||
else
|
||||
location_rollback_backend_by (location, steps,
|
||||
backend_id, TRUE);
|
||||
}
|
||||
else if (revision_id) {
|
||||
sscanf (revision_id, "%x", &id);
|
||||
if (id >= 0)
|
||||
location_rollback_id (location, id);
|
||||
else
|
||||
g_message ("Bad id specified");
|
||||
} else {
|
||||
g_message ("No backend specified");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
do_change_location (Archive *archive, Location *location)
|
||||
{
|
||||
archive_set_current_location (archive, location);
|
||||
}
|
||||
|
||||
static void
|
||||
do_rename_location (Archive *archive, Location *location)
|
||||
{
|
||||
gboolean is_current;
|
||||
|
||||
if (new_name == NULL) {
|
||||
g_message ("You did not specify a new name. Try --help");
|
||||
} else {
|
||||
if (!strcmp (location_get_id (location),
|
||||
archive_get_current_location_id (archive)))
|
||||
is_current = TRUE;
|
||||
else
|
||||
is_current = FALSE;
|
||||
|
||||
location_set_id (location, new_name);
|
||||
|
||||
if (is_current)
|
||||
archive_set_current_location_id (archive, new_name);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
do_add_location (Archive *archive)
|
||||
{
|
||||
GtkObject *location;
|
||||
Location *parent_location = NULL;
|
||||
|
||||
if (parent_str != NULL)
|
||||
parent_location = archive_get_location (archive, parent_str);
|
||||
location = location_new (archive, location_id, parent_location);
|
||||
}
|
||||
|
||||
static void
|
||||
do_remove_location (Location *location)
|
||||
{
|
||||
location_delete (location);
|
||||
}
|
||||
|
||||
static void
|
||||
do_add_backend (Location *location)
|
||||
{
|
||||
location_add_backend (location, backend_id);
|
||||
}
|
||||
|
||||
static void
|
||||
do_remove_backend (Location *location)
|
||||
{
|
||||
location_remove_backend (location, backend_id);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char **argv)
|
||||
{
|
||||
Archive *archive;
|
||||
Location *location = NULL;
|
||||
|
||||
bindtextdomain (PACKAGE, GNOMELOCALEDIR);
|
||||
textdomain (PACKAGE);
|
||||
|
||||
gnomelib_register_popt_table (global_options,
|
||||
_("Global archiver options"));
|
||||
gnomelib_register_popt_table (archiver_operations,
|
||||
_("Archiver commands"));
|
||||
gnomelib_register_popt_table (rollback_options,
|
||||
_("Options for rolling back"));
|
||||
gnomelib_register_popt_table (add_rename_location_options,
|
||||
_("Options for adding or renaming " \
|
||||
"locations"));
|
||||
gnomelib_register_popt_table (add_remove_backend_options,
|
||||
_("Options for adding and removing " \
|
||||
"backends"));
|
||||
|
||||
gtk_type_init ();
|
||||
gnomelib_init ("archiver", VERSION);
|
||||
gnomelib_parse_args (argc, argv, 0);
|
||||
|
||||
archive = ARCHIVE (archive_load (global));
|
||||
|
||||
if (archive == NULL)
|
||||
g_error ("Could not open archive");
|
||||
|
||||
if (location_id == NULL)
|
||||
location_id = archive_get_current_location_id (archive);
|
||||
|
||||
if (!add_location) {
|
||||
location = archive_get_location (archive, location_id);
|
||||
|
||||
if (location == NULL) {
|
||||
if (strcmp (location_id, "default")) {
|
||||
g_message ("Could not open location");
|
||||
return -1;
|
||||
} else {
|
||||
location = LOCATION
|
||||
(location_new (archive, location_id,
|
||||
NULL));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (store)
|
||||
do_store (location);
|
||||
else if (rollback)
|
||||
do_rollback (location);
|
||||
else if (change_location)
|
||||
do_change_location (archive, location);
|
||||
else if (rename_location)
|
||||
do_rename_location (archive, location);
|
||||
else if (add_location)
|
||||
do_add_location (archive);
|
||||
else if (remove_location)
|
||||
do_remove_location (location);
|
||||
else if (add_backend)
|
||||
do_add_backend (location);
|
||||
else if (remove_backend)
|
||||
do_remove_backend (location);
|
||||
|
||||
archive_close (archive);
|
||||
|
||||
return 0;
|
||||
}
|
97
archiver/util.c
Normal file
97
archiver/util.c
Normal file
|
@ -0,0 +1,97 @@
|
|||
/* -*- mode: c; style: linux -*- */
|
||||
|
||||
/* util.c
|
||||
* Copyright (C) 2000 Helix Code, Inc.
|
||||
*
|
||||
* Written by Bradford Hovinen (hovinen@helixcode.com)
|
||||
*
|
||||
* 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, 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., 59 Temple Place - Suite 330, Boston, MA
|
||||
* 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "util.h"
|
||||
|
||||
/* Read a fixed-digit number from a string, advancing the string
|
||||
* pointer. Return TRUE if the extraction was successful, FALSE if
|
||||
* there was no number to extract or if the number was too short.
|
||||
*/
|
||||
|
||||
gboolean
|
||||
extract_number (char **str, int *number, int digits)
|
||||
{
|
||||
char buf[64];
|
||||
|
||||
if (!isdigit (**str)) return FALSE;
|
||||
if (digits > 63) digits = 63;
|
||||
|
||||
strncpy (buf, *str, digits);
|
||||
buf[digits] = '\0';
|
||||
*number = atoi (buf);
|
||||
if (strlen (buf) < digits) return FALSE;
|
||||
*str += digits;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
struct tm *
|
||||
parse_date (char *str)
|
||||
{
|
||||
struct tm *date;
|
||||
gboolean ok;
|
||||
gint value;
|
||||
|
||||
ok = extract_number (&str, &value, 4);
|
||||
if (!ok) return NULL;
|
||||
|
||||
date = g_new (struct tm, 1);
|
||||
date->tm_year = value;
|
||||
date->tm_mon = 12;
|
||||
date->tm_mday = 31;
|
||||
date->tm_hour = 23;
|
||||
date->tm_min = 59;
|
||||
date->tm_sec = 59;
|
||||
|
||||
if (extract_number (&str, &value, 2))
|
||||
date->tm_mon = value;
|
||||
else
|
||||
return date;
|
||||
|
||||
if (extract_number (&str, &value, 2))
|
||||
date->tm_mday = value;
|
||||
else
|
||||
return date;
|
||||
|
||||
if (extract_number (&str, &value, 2))
|
||||
date->tm_hour = value;
|
||||
else
|
||||
return date;
|
||||
|
||||
if (extract_number (&str, &value, 2))
|
||||
date->tm_min = value;
|
||||
else
|
||||
return date;
|
||||
|
||||
if (extract_number (&str, &value, 2))
|
||||
date->tm_sec = value;
|
||||
|
||||
return date;
|
||||
}
|
33
archiver/util.h
Normal file
33
archiver/util.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
/* -*- mode: c; style: linux -*- */
|
||||
|
||||
/* util.h
|
||||
* Copyright (C) 2000 Helix Code, Inc.
|
||||
*
|
||||
* Written by Bradford Hovinen (hovinen@helixcode.com)
|
||||
*
|
||||
* 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, 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., 59 Temple Place - Suite 330, Boston, MA
|
||||
* 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef __UTIL_H
|
||||
#define __UTIL_H
|
||||
|
||||
#include <time.h>
|
||||
#include <glib.h>
|
||||
|
||||
gboolean extract_number (char **str, int *number, int digits);
|
||||
struct tm *parse_date (char *str);
|
||||
|
||||
#endif /* __UTIL_H */
|
193
archiver/versioning-spec
Normal file
193
archiver/versioning-spec
Normal file
|
@ -0,0 +1,193 @@
|
|||
Configuration rollback, location management, and cluster support
|
||||
Copyright (C) 2000 Helix Code, Inc.
|
||||
Written by Bradford Hovinen <hovinen@helixcode.com>
|
||||
|
||||
I. Basic architecture
|
||||
|
||||
A. Components
|
||||
|
||||
1. Helix Configuration Manager
|
||||
|
||||
The GUI shell, here referred to as the Helix Configuration Manager
|
||||
(HCM), acts as launching point for the capplets and Helix Setup Tools,
|
||||
and a control center for managing rollback, location management, and
|
||||
clustering. It launches other components and knows how to find
|
||||
archived configuration data for rollback. When rollback or a change of
|
||||
location is required, it invokes the required backends or capplets
|
||||
with the --set option and feeds the required XML to them.
|
||||
|
||||
2. Capplets
|
||||
|
||||
Capplets handle user configuration; they combine the front and back
|
||||
ends into one process. They will eventually run as Bonobo controls
|
||||
implementing the Bonobo::Capplet interface, but for now they are run
|
||||
as regular processes. They all support the --get and --set command
|
||||
line options (which will be methods in the Bonobo::Capplet
|
||||
interface). --get returns an XML description of the capplet's state
|
||||
for archival purposes and --set takes an XML description of the
|
||||
capplet's state and applies those settings to the desktop.
|
||||
|
||||
3. Helix Setup Tools (HSTs)
|
||||
|
||||
These programs are for system-wide configuration and must run as root
|
||||
in order to apply changes. They may also run as a regular user in
|
||||
`read-only' mode. They have separate front- and backends, the former
|
||||
typically written in C and the latter normally written in Perl. This
|
||||
facilitates in-place modification of existing configuration files
|
||||
without the need for creating out own separate, incompatible way of
|
||||
configuring the system. The backends support the --get and --set
|
||||
arguments, analogous to the arguments in capplets mentioned above.
|
||||
|
||||
2. Root manager
|
||||
|
||||
The root manager process runs as root and is launched through
|
||||
gnome-su. It accepts on stdin a set of programs to launch, one per
|
||||
line, with command line arguments. HCM uses it to launch Helix Setup
|
||||
Tools so that they run as root, without needing to ask the user for a
|
||||
password each time a tool is run. The root manager is run exactly once
|
||||
through console-helper the first time a tool that must be run as root
|
||||
is invoked. On subsequent occasions the command to start the tool is
|
||||
passed to the root manager through stdin.
|
||||
|
||||
3. The script do-changes
|
||||
|
||||
do-changes is responsible for archiving changes made to the system's
|
||||
configuration and passing them on to the backend, if appropriate. It
|
||||
accepts a stream of XML on stdin and stores this XML in the
|
||||
configuration archive directory. If a backend is specified on the
|
||||
command line, it also spawns the backend process with the --set option
|
||||
and feeds the XML to it through stdin.
|
||||
|
||||
II. Configuration process
|
||||
|
||||
When a user makes changes to either his own configuration or that of
|
||||
the system, those changes must be archived so that the system may be
|
||||
rolled back in the future. In the case of capplets, the capplet
|
||||
currently dumps an XML snapshot of its current state to the script
|
||||
do-changes when the user clicks `Ok'. do-changes then archives the
|
||||
state in ~/.gnome/config/<location>/<revision> where <location> is the
|
||||
name of the active location (cf. section IV) and <revision> is
|
||||
incremented after each change.
|
||||
|
||||
When the capplets are converted into Bonobo controls, the situation
|
||||
will be slightly different. HCM will be the recipient of the `Ok'
|
||||
signal, so it will invoke the OKClicked() method of the
|
||||
Bonobo::Capplet interface on the appropriate capplet. It will also
|
||||
invoke the GetXML() method of the same interface in order to retrieve
|
||||
an XML snapshot of the state and store that snapshot with
|
||||
do-changes. Hence, much of the action moves from the capplet to HCM.
|
||||
|
||||
In the case of Helix Setup Tools, the frontend passes the XML through
|
||||
the do-changes script to the backend whenever the `Ok' button is
|
||||
clicked. It passes to do-changes the argument --backend <backend name>
|
||||
so that do-changes will also invoke the indicated backend and pass the
|
||||
XML to it.
|
||||
|
||||
III. Rollback process
|
||||
|
||||
From within the HCM, the user may elect to roll back either his
|
||||
personal configuration or that of the system to a particular
|
||||
date. HCM looks for a revision directory in the current location
|
||||
profile with the most recent modification date that is not more recent
|
||||
than the date specified by the user. HCM also has a list of what
|
||||
capplets (or HSTs) constitute a complete snapshot of the system's
|
||||
configuration. In order to perform a complete rollback, it backtracks
|
||||
through the revision directories, picking up XML snapshots of capplets
|
||||
until it has a complete set and applies them through the --set
|
||||
method. In the case of HSTs, the HCM knows how to invoke the backend
|
||||
and does so as necessary.
|
||||
|
||||
IV. Location management
|
||||
|
||||
The system may have one or more profiles, each giving different system
|
||||
configurations. For example, a user may own a laptop and wish to hook
|
||||
that laptop up to different networks at different times. Each network
|
||||
may be located in a different time zone, have different network
|
||||
configuration parameters (e.g., DHCP vs. static IPs), and use different
|
||||
default printers. When the user hooks his laptop up in a particular
|
||||
network, it would be advantageous to switch to that network's
|
||||
configuration with a minimum of hassle.
|
||||
|
||||
As mentioned above, configuration data is stored in separate
|
||||
directories corresponding to the name of a given location. HCM has the
|
||||
ability to apply a set of configuration files from a given location in
|
||||
a manner similar to the rollback procedure described above. When the
|
||||
user selects an alternative configuration, it simply goes through the
|
||||
revision history for that location, pulls a complete set of
|
||||
configuration files, and applies them. The procedure is similar for
|
||||
both capplets and HSTs.
|
||||
|
||||
In addition, locations may be expressed hierarchically. For example, a
|
||||
user might specify a location called `Boston' that describes language,
|
||||
time zone, currency, and other locale data, and another location called
|
||||
`Boston Office' that includes network parameters. `Boston Office'
|
||||
inherits its locale data from `Boston', overriding the latter's
|
||||
network configuration.
|
||||
|
||||
To implement this, each location directory contains some metadata that
|
||||
describes what configuration data is valid for it and what other
|
||||
configuration it inherits from. There are one or more root
|
||||
configurations that contain a complete set of data. When applying a
|
||||
new location, HCM looks first at that location's directory, pulling a
|
||||
complete set of all the configuration data defined by that location,
|
||||
and then goes to the next level up in the location hierarchy and does
|
||||
the same thing. It also keeps track of the common subtree root between
|
||||
the old and new locations so that only the configuration items that
|
||||
actually change are collected.
|
||||
|
||||
From a user's perspective, the HCM will present a tree showing the
|
||||
existing locations. Users may create a new location derived from an
|
||||
existing one. When the user elects to configure a particular location,
|
||||
the HCM shell includes icons that are grayed out, indicating that
|
||||
those configuration items are not set in this particular location. If
|
||||
the user attempts to change them, they become specific to that
|
||||
particular location and are recolored accordingly.
|
||||
|
||||
V. Clustering
|
||||
|
||||
A single server may archive the configuration for a large number of
|
||||
individual workstations, providing configuration data to each of the
|
||||
clients on demand. An administrator can then push configuration
|
||||
updates out to each machine with the press of a button, rather than
|
||||
having to go to each machine and update it manually.
|
||||
|
||||
To enable this, each client machine will run a daemon that accepts
|
||||
configuration data pushed out by the server. Some sort of public key
|
||||
signing will be implemented to ensure that this is done securely. On
|
||||
the server end, a series of host groups is maintained, each one
|
||||
containing a set of hosts. These form the top two levels of a
|
||||
configuration hierarchy not unlike what is described above. Each host
|
||||
may override certain configuration values for the cluster as a
|
||||
whole. The cluster may also have multiple `locations', e.g. for
|
||||
configuring a computer lab for computer science during one class and
|
||||
for math during another. Locations may be selected down to the
|
||||
granularity of a single host, or for the entire cluster at
|
||||
once. Cluster-wide configurations occur between the cluster and host
|
||||
level in the configuration hierarchy.
|
||||
|
||||
VI. Issues
|
||||
|
||||
1. We need a way to get an XML state without actually applying
|
||||
changes, so that the user can configure a location without switching
|
||||
to it.
|
||||
|
||||
2. Can we make the HST frontends Bonobo controls, and can we have them
|
||||
run as the regular user rather than as root? This would ensure that
|
||||
certain user interface preferences, such as themes, are kept
|
||||
consistent for a given user between capplets and HSTs. The way to
|
||||
implement this is to have a method on the HCM interface called
|
||||
RunBackend() which returns a BonoboObject referring to the backend
|
||||
that implements the Bonobo::HSTBackend interface, which is similar to
|
||||
the Bonobo::Capplet interface mentioned above. The interface defines
|
||||
the GetXML and SetXML methods. The object should also implement
|
||||
another, HST-specific interface to facilitate setting specific
|
||||
configuration variables, so that live update may be implemented. The
|
||||
root manager must then be extended to support some sort of secure
|
||||
forwarding, allowing the user to access that particular CORBA object.
|
||||
|
||||
3. If we make the HSTs into Bonobo controls, can we give them the same
|
||||
Bonobo::Capplet interface that is given to the Capplets? This would
|
||||
make everything a bit simpler from the HCM's perspective, since it
|
||||
then does not need to know the difference between Capplets and
|
||||
HSTs -- it then only needs to implement the RunBackend() method for
|
||||
the benefit of the HSTs.
|
Loading…
Add table
Add a link
Reference in a new issue