/*
** Copyright (c) 2008 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public
** License version 2 as published by the Free Software Foundation.
**
** 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 library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code used to manage repository configurations.
** By "responsitory configure" we mean the local state of a repository
** distinct from the versioned files.
*/
#include "config.h"
#include "configure.h"
#include <assert.h>
#if INTERFACE
/*
** Configuration transfers occur in groups. These are the allowed
** groupings:
*/
#define CONFIGSET_SKIN 0x000001 /* WWW interface appearance */
#define CONFIGSET_TKT 0x000002 /* Ticket configuration */
#define CONFIGSET_PROJ 0x000004 /* Project name */
#define CONFIGSET_ALL 0xffffff /* Everything */
#endif /* INTERFACE */
/*
** Names of the configuration sets
*/
static struct {
const char *zName; /* Name of the configuration set */
int groupMask; /* Mask for that configuration set */
} aGroupName[] = {
{ "skin", CONFIGSET_SKIN },
{ "ticket", CONFIGSET_TKT },
{ "project", CONFIGSET_PROJ },
{ "all", CONFIGSET_ALL },
};
/*
** The following is a list of settings that we are willing to
** transfer.
*/
static struct {
const char *zName; /* Name of the configuration parameter */
int groupMask; /* Which config groups is it part of */
} aConfig[] = {
{ "css", CONFIGSET_SKIN },
{ "header", CONFIGSET_SKIN },
{ "footer", CONFIGSET_SKIN },
{ "project-name", CONFIGSET_PROJ },
{ "project-description", CONFIGSET_PROJ },
{ "index-page", CONFIGSET_SKIN },
{ "timeline-block-markup", CONFIGSET_SKIN },
{ "timeline-max-comment", CONFIGSET_SKIN },
{ "ticket-table", CONFIGSET_TKT },
{ "ticket-common", CONFIGSET_TKT },
{ "ticket-newpage", CONFIGSET_TKT },
{ "ticket-viewpage", CONFIGSET_TKT },
{ "ticket-editpage", CONFIGSET_TKT },
};
static int iConfig = 0;
/*
** Return name of first configuration property matching the given mask.
*/
const char *configure_first_name(int iMask){
iConfig = 0;
return configure_next_name(iMask);
}
const char *configure_next_name(int iMask){
while( iConfig<count(aConfig) ){
if( aConfig[iConfig].groupMask & iMask ){
return aConfig[iConfig++].zName;
}else{
iConfig++;
}
}
return 0;
}
/*
** Return TRUE if a particular configuration parameter zName is
** safely exportable.
*/
int configure_is_exportable(const char *zName){
int i;
for(i=0; i<count(aConfig); i++){
if( strcmp(zName, aConfig[i].zName)==0 ){
return aConfig[i].groupMask;
}
}
return 0;
}
/*
** Identify a configuration group by name. Return its mask.
** Throw an error if no match.
*/
static int find_area(const char *z){
int i;
int n = strlen(z);
for(i=0; i<count(aGroupName); i++){
if( strncmp(z, aGroupName[i].zName, n)==0 ){
return aGroupName[i].groupMask;
}
}
fossil_fatal("no such configuration area: \"%s\"", z);
return 0;
}
/*
** COMMAND: configuration
**
** Usage: %fossil configure METHOD ...
**
** Where METHOD is one of: export import pull reset. All methods
** accept the -R or --repository option to specific a repository.
**
** %fossil configuration export AREA FILENAME
**
** Write to FILENAME exported configuraton information for AREA.
** AREA can be one of: all ticket skin project
**
** %fossil configuration import FILENAME
**
** Read a configuration from FILENAME, overwriting the current
** configuration. Warning: Do not read a configuration from
** an untrusted source since the configuration is not checked
** for safety and can introduce security threats.
**
** %fossil configuration pull AREA URL
**
** Pull and install the configuration from a different server
** identified by URL. AREA is as in "export".
**
** %fossil configuration reset AREA
**
** Restore the configuration to the default. AREA as above.
*/
void configuration_cmd(void){
int n;
const char *zMethod;
if( g.argc<3 ){
usage("METHOD ...");
}
db_find_and_open_repository(1);
zMethod = g.argv[2];
n = strlen(zMethod);
if( strncmp(zMethod, "export", n)==0 ){
int i;
int mask;
const char *zSep;
Blob sql;
Stmt q;
Blob out;
if( g.argc!=5 ){
usage("export AREA FILENAME");
}
mask = find_area(g.argv[3]);
blob_zero(&sql);
blob_zero(&out);
blob_appendf(&sql,
"SELECT 'REPLACE INTO config(name,value) VALUES('''"
" || name || ''',' || quote(value) || ');'"
" FROM config WHERE name IN "
);
zSep = "(";
for(i=0; i<count(aConfig); i++){
if( aConfig[i].groupMask & mask ){
blob_appendf(&sql, "%s'%s'", zSep, aConfig[i].zName);
zSep = ",";
}
}
blob_appendf(&sql, ") ORDER BY name");
db_prepare(&q, blob_str(&sql));
blob_reset(&sql);
blob_appendf(&out,
"-- The \"%s\" configuration exported from\n"
"-- repository \"%s\"\n"
"-- on %s\n",
g.argv[3], g.zRepositoryName,
db_text(0, "SELECT datetime('now')")
);
while( db_step(&q)==SQLITE_ROW ){
blob_appendf(&out, "%s\n", db_column_text(&q, 0));
}
db_finalize(&q);
blob_write_to_file(&out, g.argv[4]);
blob_reset(&out);
}else
if( strncmp(zMethod, "import", n)==0 ){
Blob in;
if( g.argc!=4 ) usage("import FILENAME");
blob_read_from_file(&in, g.argv[3]);
db_begin_transaction();
db_multi_exec("%s", blob_str(&in));
db_end_transaction(0);
}else
if( strncmp(zMethod, "pull", n)==0 ){
int mask;
url_proxy_options();
if( g.argc!=5 ) usage("pull AREA URL");
mask = find_area(g.argv[3]);
url_parse(g.argv[4]);
if( g.urlIsFile ){
fossil_fatal("network sync only");
}
user_select();
client_sync(0,0,0,mask);
}else
if( strncmp(zMethod, "reset", n)==0 ){
int mask, i;
if( g.argc!=4 ) usage("reset AREA");
mask = find_area(g.argv[3]);
db_begin_transaction();
for(i=0; i<count(aConfig); i++){
if( (aConfig[i].groupMask & mask)==0 ) continue;
db_multi_exec("DELETE FROM config WHERE name=%Q", aConfig[i].zName);
}
db_end_transaction(0);
}else
{
fossil_fatal("METHOD should be one of: export import pull reset");
}
}