/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#if !defined(ORG_FOSSIL_SCM_FSL_CONFDB_H_INCLUDED)
#define ORG_FOSSIL_SCM_FSL_CONFDB_H_INCLUDED
/*
  Copyright 2013-2021 The Libfossil Authors, see LICENSES/BSD-2-Clause.txt

  SPDX-License-Identifier: BSD-2-Clause-FreeBSD
  SPDX-FileCopyrightText: 2021 The Libfossil Authors
  SPDX-ArtifactOfProjectName: Libfossil
  SPDX-FileType: Code

  Heavily indebted to the Fossil SCM project (https://fossil-scm.org).
*/
/** @file fossil-confdb.h

    fossil-confdb.h declares APIs dealing with fossil's various
    configuration option storage backends.
*/

#include "core.h" /* MUST come first b/c of config macros */

#if defined(__cplusplus)
extern "C" {
#endif

/**
   A flag type for specifying which configuration backend a given API
   should be applied to. Used by most of the fsl_config_XXX() APIS,
   e.g. fsl_config_get_int32() and friends.

   This type is named fsl_confdb_e (not the "db" part) because 3 of
   the 4 fossil-supported config storage backends are databases. The
   "versioned setting" backend, which deals with non-db local files,
   was encapsulated into this API long after the db-related options
   were
*/
enum fsl_confdb_e {
/** Sentinel value. Not for use with public APIs. */
FSL_CONFDB_NONE = 0,
/**
   Signfies the global-level (per system user) configuration area.

   Note that fsl_config_... APIs which use this do not currently open
   that database on demand due to the potential for downstream
   locking-related issues when the config db is left open, as well as
   locking-related issues here when a separate client or instance is
   holding that db open. That behaviour may change in the future.
*/
FSL_CONFDB_GLOBAL = 1,
/**
   Signfies the repository-level configuration area.
*/
FSL_CONFDB_REPO = 2,
/**
   Signfies the checkout-level (a.k.a. "local") configuration area.
*/
FSL_CONFDB_CKOUT = 3,

/**
   The obligatory special case...

   Versionable settings are stored directly in SCM-controlled files,
   each of which has the same name as the setting and lives in the
   .fossil-settings directory of a checkout. Though versionable
   settings _can_ be read from a non-checked-out repository, doing so
   requires knowning which version to fetch and is horribly
   inefficient, so there are currently no APIs for doing so.

   Note that the APIs which read and write versioned settings do not
   care whether those settings are valid for fossil(1).

   Reminder to self: SQL to find the checkins in which a versioned
   settings file was added or modified (ignoring renames and branches
   and whatnot).

   ```
select strftime('%Y-%m-%d %H:%M:%S',e.mtime) mtime,
b.rid, b.uuid from mlink m, filename f, blob b, event e
where f.fnid=m.fnid
and m.mid=b.rid
and b.rid=e.objid
and f.name='.fossil-settings/ignore-glob'
order by e.mtime desc 
;
   ```

*/
FSL_CONFDB_VERSIONABLE = 4
};
typedef enum fsl_confdb_e fsl_confdb_e;

/**
   Returns the name of the db table associated with the given
   mode. Results are undefined if mode is an invalid value. The
   returned bytes are static and constant.

   Returns NULL for the role FSL_CONFDB_VERSIONABLE.
*/
FSL_EXPORT char const * fsl_config_table_for_role(fsl_confdb_e mode);

/**
   Returns a handle to the db associates with the given fsl_confdb_e
   value. Returns NULL if !f or if f has no db opened for that
   configuration role. Results are undefined if mode is an invalid
   value.

   For FSL_CONFDB_VERSIONABLE it returns the results of fsl_cx_db(),
   even though there is no database-side support for versionable files
   (which live in files in a checkout).
*/
FSL_EXPORT fsl_db * fsl_config_for_role(fsl_cx * const f, fsl_confdb_e mode);

/**
   Returns the int32 value of a property from one of f's config
   dbs, as specified by the mode parameter. Returns dflt if !f, f
   does not have the requested config db opened, no entry is found,
   or on db-level errors.
*/
FSL_EXPORT int32_t fsl_config_get_int32( fsl_cx * const f, fsl_confdb_e mode,
                                         int32_t dflt, char const * key );
/**
   int64_t counterpart of fsl_config_get_int32().
*/
FSL_EXPORT int64_t fsl_config_get_int64( fsl_cx * const f, fsl_confdb_e mode,
                                         int64_t dflt, char const * key );

/**
   fsl_id_t counterpart of fsl_config_get_int32().
*/
FSL_EXPORT fsl_id_t fsl_config_get_id( fsl_cx * const f, fsl_confdb_e mode,
                                       fsl_id_t dflt, char const * key );
/**
   double counterpart of fsl_config_get_int32().
*/
FSL_EXPORT double fsl_config_get_double( fsl_cx * const f, fsl_confdb_e mode,
                                         double dflt, char const * key );


/**
   Boolean countertpart of fsl_config_get_int32().

   fsl_str_bool() is used to determine the booleanness (booleanity?)
   of a given config option.
*/
FSL_EXPORT bool fsl_config_get_bool( fsl_cx * const f, fsl_confdb_e mode,
                                     bool dflt, char const * key );

/**
   A convenience form of fsl_config_get_buffer().  If it finds a
   config entry it returns its value. If *len is not NULL then *len is
   assigned the length of the returned string, in bytes (and is set to
   0 if NULL is returned). Any errors encounters while looking for
   the entry are suppressed and NULL is returned.

   The returned memory must eventually be freed using fsl_free(). If
   len is not NULL then it is set to the length of the returned
   string. Returns NULL for any sort of error or for a NULL db value.

   If capturing error state is important for a given use case, use
   fsl_config_get_buffer() instead, which provides the same features
   as this one but propagates any error state.
*/
FSL_EXPORT char * fsl_config_get_text( fsl_cx * const f, fsl_confdb_e mode,
                                       char const * key,
                                       fsl_size_t * len );

/**
   The fsl_buffer-type counterpart of fsl_config_get_int32().

   Replaces the contents of the given buffer (re-using any memory it
   might have) with a value from a config region. Returns 0 on
   success, FSL_RC_NOT_FOUND if no entry was found or the requested db
   is not opened, FSL_RC_OOM on allocation errror.

   If mode is FSL_CONFDB_VERSIONABLE, this operation requires
   a checkout and returns (if possible) the contents of the file
   named {CHECKOUT_ROOT}/.fossil-settings/{key}.

   On any sort of error, including the inability to find or open
   a versionable-settings file, non-0 is returned:

   - FSL_RC_OOM on allocation error

   - FSL_RC_NOT_FOUND if either the database referred to by mode is
   not opened or a matching setting cannot be found.

   - FSL_RC_ACCESS is a versionable setting file is found but cannot
   be opened for reading.

   - FSL_RC_NOT_A_CKOUT if mode is FSL_CONFDB_VERSIONABLE and no
   checkout is opened.

   - Potentially one of several db-related codes if reading a
   non-versioned setting fails.

   In the grand scheme of things, the inability to load a setting is
   not generally an error, so clients are not expected to treat it as
   fatal unless perhaps it returns FSL_RC_OOM, in which case it likely
   is.

   @see fsl_config_has_versionable()
*/
FSL_EXPORT int fsl_config_get_buffer( fsl_cx * const f, fsl_confdb_e mode,
                                      char const * key, fsl_buffer * const b );


/**
   If f has an opened checkout, this replaces b's contents (re-using
   any existing memory) with an absolute path to the filename for that
   setting: {CHECKOUT_ROOT}/.fossil-settings/{key}

   This routine neither verifies that the given key is a valid
   versionable setting name nor that the file exists: it's purely a
   string operation.

   Returns 0 on success, FSL_RC_NOT_A_CKOUT if f has no checkout
   opened, FSL_RC_MISUSE if key is NULL, empty, or if
   fsl_is_simple_pathname() returns false for the key, and FSL_RC_OOM
   if appending to the buffer fails.
 */
FSL_EXPORT int fsl_config_versionable_filename(fsl_cx *f, char const * key, fsl_buffer *b);

/**
   Sets a configuration variable in one of f's config databases, as
   specified by the mode parameter. Returns 0 on success.  val may
   be NULL. Returns FSL_RC_MISUSE if !f, f does not have that
   database opened, or !key, FSL_RC_RANGE if !key.

   If mode is FSL_CONFDB_VERSIONABLE, it attempts to write/overwrite
   the corresponding file in the current checkout.

   If mem is NULL and mode is not FSL_CONFDB_VERSIONABLE then an SQL
   NULL is bound instead of an empty blob. For FSL_CONFDB_VERSIONABLE
   an empty file will be written for that case.

   If mode is FSL_CONFDB_VERSIONABLE, this does NOT queue any
   newly-created versionable setting file for inclusion into the
   SCM. That is up to the caller. See
   fsl_config_versionable_filename() for info about an additional
   potential usage error case with FSL_CONFDB_VERSIONABLE.

   Pedantic side-note: the input text is saved as-is. No trailing
   newline is added when saving to FSL_CONFDB_VERSIONABLE because
   doing so would require making a copy of the input bytes just to add
   a newline to it. The non-text fsl_config_set_XXX() APIs add a
   newline when writing to their values out to a versionable config
   file because it costs them nothing to do so and text files "should"
   have a trailing newline.

   Potential TODO: if mode is FSL_CONFDB_VERSIONABLE and the key
   contains directory components, e,g, "app/x", we should arguably use
   fsl_mkdir_for_file() to create those components. As of this writing
   (2021-03-14), no such config keys have ever been used in fossil.

   @see fsl_config_versionable_filename()
*/
FSL_EXPORT int fsl_config_set_text( fsl_cx * const f, fsl_confdb_e mode, char const * key, char const * val );

/**
   The blob counterpart of fsl_config_set_text(). If len is
   negative then fsl_strlen(mem) is used to determine the length of
   the memory.

   If mem is NULL and mode is not FSL_CONFDB_VERSIONABLE then an SQL
   NULL is bound instead of an empty blob. For FSL_CONFDB_VERSIONABLE
   an empty file will be written for that case.
*/
FSL_EXPORT int fsl_config_set_blob( fsl_cx * const f, fsl_confdb_e mode, char const * key,
                                    void const * mem, fsl_int_t len );
/**
   int32 counterpart of fsl_config_set_text().
*/
FSL_EXPORT int fsl_config_set_int32( fsl_cx * const f, fsl_confdb_e mode,
                                     char const * key, int32_t val );
/**
   int64 counterpart of fsl_config_set_text().
*/
FSL_EXPORT int fsl_config_set_int64( fsl_cx * const f, fsl_confdb_e mode,
                                     char const * key, int64_t val );
/**
   fsl_id_t counterpart of fsl_config_set_text().
*/
FSL_EXPORT int fsl_config_set_id( fsl_cx * const f, fsl_confdb_e mode,
                                  char const * key, fsl_id_t val );
/**
   fsl_double counterpart of fsl_config_set_text().
*/
FSL_EXPORT int fsl_config_set_double( fsl_cx * const f, fsl_confdb_e mode,
                                      char const * key, double val );
/**
   Boolean counterpart of fsl_config_set_text().

   For compatibility with fossil conventions, the value will be saved
   in the string form "on" or "off". When mode is
   FSL_CONFDB_VERSIONABLE, that value will include a trailing newline.
*/
FSL_EXPORT int fsl_config_set_bool( fsl_cx * const f, fsl_confdb_e mode,
                                    char const * key, bool val );

/**
   "Unsets" (removes) the given key from the given configuration database.
   It is not considered to be an error if the config table does not
   contain that key.

   Returns FSL_RC_UNSUPPORTED, without side effects, if mode is
   FSL_CONFDB_VERSIONABLE. It "could" hypothetically remove a
   checked-out copy of a versioned setting, then queue the file for
   removal in the next checkin, but it does not do so. It might, in
   the future, be changed to do so, or at least to remove the local
   settings file.
*/
FSL_EXPORT int fsl_config_unset( fsl_cx * const f, fsl_confdb_e mode,
                                 char const * key );

/**
   Begins (or recurses) a transaction on the given configuration
   database.  Returns 0 on success, non-0 on error. On success,
   fsl_config_transaction_end() must eventually be called with the
   same parameters to pop the transaction stack. Returns
   FSL_RC_MISUSE if no db handle is opened for the given
   configuration mode. Assuming all arguments are valid, this
   returns the result of fsl_db_transaction_end() and propagates
   any db-side error into the f object's error state.

   This is primarily intended as an optimization when an app is
   making many changes to a config database. It is not needed when
   the app is only making one or two changes.

   @see fsl_config_transaction_end()
   @see fsl_db_transaction_begin()
*/
FSL_EXPORT int fsl_config_transaction_begin(fsl_cx * const f, fsl_confdb_e mode);

/**
   Pops the transaction stack pushed by
   fsl_config_transaction_begin(). If rollback is true then the
   transaction is set roll back, otherwise it is allowed to
   continue (if recursive) or committed immediately (if not
   recursive).  Returns 0 on success, non-0 on error. Returns
   FSL_RC_MISUSE if no db handle is opened for the given
   configuration mode. Assuming all arguments are valid, this
   returns the result of fsl_db_transaction_end() and propagates
   any db-side error into the f object's error state.

   @see fsl_config_transaction_begin()
   @see fsl_db_transaction_end()
*/
FSL_EXPORT int fsl_config_transaction_end(fsl_cx * const f, fsl_confdb_e mode, bool rollback);

/**
   Populates li as a glob list from the given configuration key.
   Uses (versionable/repo/global) config settings, in that order.
   It is not an error if one or more of those sources is missing -
   they are simply skipped.

   Note that gets any new globs appended to it, as per
   fsl_glob_list_append(), as opposed to replacing any existing
   contents.

   Returns 0 on success, but that only means that there were no
   errors, not that any entries were necessarily added to li.

   Arguably a bug: this function does not open the global config if
   it was not already opened, but will use it if it is opened. This
   function should arbuably open and close it in that case. That said,
   there are cases which prohibit closing of the config db and this
   function cannot know if one of those cases is active.
*/
FSL_EXPORT int fsl_config_globs_load(fsl_cx * const f, fsl_list * const li, char const * key);

/**
   Fetches the preferred name of the "global" db file for the current
   user by assigning it to *zOut. Returns 0 on success, in which case
   *zOut is updated and non-0 on error, in which case *zOut is not
   modified.  On success, ownership of *zOut is transferred to the
   caller, who must eventually free it using fsl_free().

   The locations searched for the database file are
   platform-dependent...

   Unix-like systems are searched in the following order:

   1) If the FOSSIL_HOME environment var is set, use
   $FOSSIL_HOME/.fossil.

   2) If $HOME/.fossil already exists, use that.

   3) If XDG_CONFIG_HOME environment var is set, use
   $XDG_CONFIG_HOME/fossil.db.

   4) If $HOME/.config is a directory, use $HOME/.config/fossil.db

   5) Fall back to $HOME/.fossil (historical name).

   Except where listed above, this function does not check whether the
   file already exists or is a database.

   Windows:

   - We need a Windows port of this routine. Currently it `#error`'s
   out at compile-time on Windows.
*/
FSL_EXPORT int fsl_config_global_preferred_name(char ** zOut);

/**
   A variant of fsl_config_get_int32() which can search through multiple
   config sources in an order specified by the caller.

   zCfg must be a NUL-terminated list of case-sensitive letters
   corresponding to config sources: any of (c, r, v, g) for checkout,
   repo, versionable, and global, respectively. Each source, if
   available, is searched, in the order provided, until either the
   given key is found in that source or no more sources are available,
   in which case dflt is returned. Any missing sources are silently
   skipped over (e.g. if 'c' is provided by no checkout is opened,
   that is not considered an error by this function).

   Errors are silently ignored and cause dflt to be returned.

   @see fsl_configs_get_int64()
   @see fsl_configs_get_id()
   @see fsl_configs_get_bool()
   @see fsl_configs_get_text()
*/
FSL_EXPORT int32_t fsl_configs_get_int32(fsl_cx * const f, char const * zCfg, int32_t dflt, char const * key);

/**
   The int64_t counterpart of fsl_configs_get_int32().
*/
FSL_EXPORT int64_t fsl_configs_get_int64(fsl_cx * const f, char const * zCfg, int64_t dflt, char const * key);

/**
   The fsl_id_t counterpart of fsl_configs_get_int32().
*/
FSL_EXPORT fsl_id_t fsl_configs_get_id(fsl_cx * const f, char const * zCfg, fsl_id_t dflt, char const * key);

/**
   The bool counterpart of fsl_configs_get_int32().
*/
FSL_EXPORT bool fsl_configs_get_bool(fsl_cx * const f, char const * zCfg, bool dflt, char const * key);

/**
   The double counterpart of fsl_configs_get_int32().
*/
FSL_EXPORT double fsl_configs_get_double(fsl_cx * const f, char const * zCfg, double dflt, char const * key);

/**
   The buffer counterpart of fsl_configs_get_int32(). This variant
   collects, if found, its results in the given buffer (its memory, if
   any, is reused by this routine). It returns 0 if an entry is found,
   FSL_RC_OOM on buffer allocation error, and FSL_RC_NOT_FOUND if no
   match is found in any of the listed sources (noting that missing
   sources are silently skipped). The full range of errors reported by
   fsl_config_get_buffer() is not supported here: only FSL_RC_OOM and
   FSL_RC_NOT_FOUND are reported.

   @see fsl_configs_get_text()
*/
FSL_EXPORT int fsl_configs_get_buffer( fsl_cx * const f, char const *zCfg,
                                       char const * key, fsl_buffer * const b );

/**
   The C-string counterpart of fsl_configs_get_int32(). If the final
   argument is not NULL then if a match is found then its length (in
   bytes) is assigned to (*len). Returns NULL if no match is found.

   If non-NULL is returned, ownership is transfered to the caller, who
   must eventually pass the value to fsl_free().

   Note that this function silently ignores any errors,
   e.g. allocation failure for the result string will be reported by
   returning NULL (which is indistinguishable from it not finding a
   match).

   @see fsl_configs_get_buffer()
*/
FSL_EXPORT char * fsl_configs_get_text(fsl_cx * const f, char const * zCfg, char const * key,
                                       fsl_size_t * len);

#if defined(__cplusplus)
} /*extern "C"*/
#endif
#endif
/* ORG_FOSSIL_SCM_FSL_CONFDB_H_INCLUDED */
