Merge branch 'db_refactor1'

This commit is contained in:
ejurgensen 2018-12-31 15:57:49 +01:00
commit 333c46318d
14 changed files with 1172 additions and 1614 deletions

View File

@ -122,6 +122,8 @@ FORK_MODULES_CHECK([COMMON], [SQLITE3], [sqlite3 >= 3.5.0],
[dnl Check that SQLite3 has the unlock notify API built-in
AC_CHECK_FUNC([[sqlite3_unlock_notify]], [],
[AC_MSG_ERROR([[SQLite3 was built without unlock notify support]])])
dnl Check for sqlite3_expanded_sql (optional)
AC_CHECK_FUNCS([sqlite3_expanded_sql])
dnl Check that SQLite3 has been built with threadsafe operations
AC_MSG_CHECKING([[if SQLite3 was built with threadsafe operations support]])
AC_RUN_IFELSE([AC_LANG_PROGRAM([[#include <sqlite3.h>

View File

@ -95,11 +95,13 @@ library {
# (changing this setting only takes effect after rescan, see the README)
compilations = { "/Compilations" }
# Compilations usually have many artists, and if you don't want every
# artist to be listed when artist browsing in Remote, you can set
# a single name which will be used for all music in the compilation dir
# Compilations usually have many artists, and sometimes no album artist.
# If you don't want every artist to be listed in artist views, you can
# set a single name which will be used for all compilation tracks
# without an album artist, and for all tracks in the compilation
# directories.
# (changing this setting only takes effect after rescan, see the README)
compilation_artist = "Various artists"
compilation_artist = "Various Artists"
# If your album and artist lists are cluttered, you can choose to hide
# albums and artists with only one track. The tracks will still be

View File

@ -208,6 +208,34 @@ sqlext_daap_songalbumid_xfunc(sqlite3_context *pv, int n, sqlite3_value **ppv)
sqlite3_result_int64(pv, result);
}
static void
sqlext_daap_no_zero_xfunc(sqlite3_context *pv, int n, sqlite3_value **ppv)
{
sqlite3_int64 new_value;
sqlite3_int64 old_value;
if (n != 2)
{
sqlite3_result_error(pv, "daap_no_zero() requires 2 parameters, new_value and old_value", -1);
return;
}
if ((sqlite3_value_type(ppv[0]) != SQLITE_INTEGER)
|| (sqlite3_value_type(ppv[1]) != SQLITE_INTEGER))
{
sqlite3_result_error(pv, "daap_no_zero() requires 2 integer parameters", -1);
return;
}
new_value = sqlite3_value_int64(ppv[0]);
old_value = sqlite3_value_int64(ppv[1]);
if (new_value != 0)
sqlite3_result_int64(pv, new_value);
else
sqlite3_result_int64(pv, old_value);
}
static int
sqlext_daap_unicode_xcollation(void *notused, int llen, const void *left, int rlen, const void *right)
{
@ -259,6 +287,15 @@ sqlite3_extension_init(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines
return -1;
}
ret = sqlite3_create_function(db, "daap_no_zero", 2, SQLITE_UTF8, NULL, sqlext_daap_no_zero_xfunc, NULL, NULL);
if (ret != SQLITE_OK)
{
if (pzErrMsg)
*pzErrMsg = sqlite3_mprintf("Could not create daap_no_zero function: %s\n", sqlite3_errmsg(db));
return -1;
}
ret = sqlite3_create_collation(db, "DAAP", SQLITE_UTF8, NULL, sqlext_daap_unicode_xcollation);
if (ret != SQLITE_OK)
{

1119
src/db.c

File diff suppressed because it is too large Load Diff

108
src/db.h
View File

@ -140,12 +140,16 @@ db_data_kind_label(enum data_kind data_kind);
/* Note that fields marked as integers in the metadata map in filescanner_ffmpeg must be uint32_t here */
struct media_file_info {
uint32_t id;
char *path;
uint32_t index;
char *virtual_path;
char *fname;
uint32_t directory_id; /* Id of directory */
char *title;
char *artist;
char *album;
char *album_artist;
char *genre;
char *comment;
char *type; /* daap.songformat */
@ -160,6 +164,7 @@ struct media_file_info {
uint32_t song_length;
int64_t file_size;
uint32_t year; /* TDRC */
uint32_t date_released;
uint32_t track; /* TRCK */
uint32_t total_tracks;
@ -167,44 +172,44 @@ struct media_file_info {
uint32_t disc; /* TPOS */
uint32_t total_discs;
uint32_t bpm; /* TBPM */
uint32_t compilation;
char artwork;
uint32_t rating;
uint32_t play_count;
uint32_t skip_count;
uint32_t seek;
uint32_t data_kind; /* dmap.datakind (asdk) */
uint32_t media_kind;
uint32_t item_kind; /* song or movie */
char *description; /* daap.songdescription */
uint32_t db_timestamp;
uint32_t time_added; /* FIXME: time_t */
uint32_t time_modified;
uint32_t time_played;
uint32_t play_count;
uint32_t seek;
uint32_t rating;
uint32_t db_timestamp;
uint32_t time_skipped;
uint32_t disabled;
uint32_t bpm; /* TBPM */
uint32_t id;
char *description; /* daap.songdescription */
uint64_t sample_count; //TODO [unused] sample count is never set and therefor always 0
char *codectype; /* song.codectype, 4 chars max (32 bits) */
uint32_t item_kind; /* song or movie */
uint32_t data_kind; /* dmap.datakind (asdk) */
uint64_t sample_count; //TODO [unused] sample count is never set and therefor always 0
uint32_t compilation;
char artwork;
uint32_t idx;
/* iTunes 5+ */
uint32_t contentrating;
uint32_t has_video; /* iTunes 6.0.2 */
uint32_t contentrating;/* iTunes 5+ */
/* iTunes 6.0.2 */
uint32_t has_video;
uint32_t bits_per_sample;
uint32_t media_kind;
uint32_t tv_episode_sort;
uint32_t tv_season_num;
char *tv_series_name;
char *tv_episode_num_str; /* com.apple.itunes.episode-num-str, used as a unique episode identifier */
char *tv_network_name;
char *album_artist;
uint32_t tv_episode_sort;
uint32_t tv_season_num;
int64_t songartistid;
int64_t songalbumid;
@ -212,16 +217,8 @@ struct media_file_info {
char *title_sort;
char *artist_sort;
char *album_sort;
char *composer_sort;
char *album_artist_sort;
char *virtual_path;
uint32_t directory_id; /* Id of directory */
uint32_t date_released;
uint32_t skip_count;
uint32_t time_skipped;
char *composer_sort;
};
#define mfi_offsetof(field) offsetof(struct media_file_info, field)
@ -308,10 +305,13 @@ struct db_group_info {
struct db_media_file_info {
char *id;
char *path;
char *virtual_path;
char *fname;
char *directory_id;
char *title;
char *artist;
char *album;
char *album_artist;
char *genre;
char *comment;
char *type;
@ -325,6 +325,7 @@ struct db_media_file_info {
char *song_length;
char *file_size;
char *year;
char *date_released;
char *track;
char *total_tracks;
char *disc;
@ -334,14 +335,17 @@ struct db_media_file_info {
char *artwork;
char *rating;
char *play_count;
char *skip_count;
char *seek;
char *data_kind;
char *media_kind;
char *item_kind;
char *description;
char *db_timestamp;
char *time_added;
char *time_modified;
char *time_played;
char *db_timestamp;
char *time_skipped;
char *disabled;
char *sample_count;
char *codectype;
@ -349,8 +353,6 @@ struct db_media_file_info {
char *has_video;
char *contentrating;
char *bits_per_sample;
char *album_artist;
char *media_kind;
char *tv_episode_sort;
char *tv_season_num;
char *tv_series_name;
@ -361,13 +363,8 @@ struct db_media_file_info {
char *title_sort;
char *artist_sort;
char *album_sort;
char *composer_sort;
char *album_artist_sort;
char *virtual_path;
char *directory_id;
char *date_released;
char *skip_count;
char *time_skipped;
char *composer_sort;
};
#define dbmfi_offsetof(field) offsetof(struct db_media_file_info, field)
@ -425,8 +422,7 @@ struct directory_enum {
void *stmt;
};
struct db_queue_item
{
struct db_queue_item {
/* A unique id for this queue item. If the same item appears multiple
times in the queue each corresponding queue item has its own id. */
uint32_t id;
@ -434,18 +430,16 @@ struct db_queue_item
/* Id of the file/item in the files database */
uint32_t file_id;
/* Length of the item in ms */
uint32_t song_length;
uint32_t pos;
uint32_t shuffle_pos;
/* Data type of the item */
enum data_kind data_kind;
/* Media type of the item */
enum media_kind media_kind;
uint32_t seek;
uint32_t pos;
uint32_t shuffle_pos;
/* Length of the item in ms */
uint32_t song_length;
char *path;
char *virtual_path;
@ -453,7 +447,6 @@ struct db_queue_item
char *title;
char *artist;
char *album_artist;
char *composer;
char *album;
char *genre;
@ -471,8 +464,15 @@ struct db_queue_item
char *artwork_url;
uint32_t queue_version;
char *composer;
/* Not saved in queue table */
uint32_t seek;
};
#define qi_offsetof(field) offsetof(struct db_queue_item, field)
struct db_queue_add_info
{
int queue_version;
@ -510,12 +510,6 @@ free_query_params(struct query_params *qp, int content_only);
void
free_queue_item(struct db_queue_item *queue_item, int content_only);
void
unicode_fixup_mfi(struct media_file_info *mfi);
void
fixup_tags_mfi(struct media_file_info *mfi);
/* Maintenance and DB hygiene */
void
db_hook_post_scan(void);

View File

@ -36,10 +36,13 @@
"CREATE TABLE IF NOT EXISTS files (" \
" id INTEGER PRIMARY KEY NOT NULL," \
" path VARCHAR(4096) NOT NULL," \
" virtual_path VARCHAR(4096) DEFAULT NULL," \
" fname VARCHAR(255) NOT NULL," \
" directory_id INTEGER DEFAULT 0," \
" title VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
" artist VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
" album VARCHAR(1024) NOT NULL COLLATE DAAP," \
" album_artist VARCHAR(1024) NOT NULL COLLATE DAAP," \
" genre VARCHAR(255) DEFAULT NULL COLLATE DAAP," \
" comment VARCHAR(4096) DEFAULT NULL COLLATE DAAP," \
" type VARCHAR(255) DEFAULT NULL COLLATE DAAP," \
@ -53,6 +56,7 @@
" song_length INTEGER DEFAULT 0," \
" file_size INTEGER DEFAULT 0," \
" year INTEGER DEFAULT 0," \
" date_released INTEGER DEFAULT 0," \
" track INTEGER DEFAULT 0," \
" total_tracks INTEGER DEFAULT 0," \
" disc INTEGER DEFAULT 0," \
@ -62,14 +66,17 @@
" artwork INTEGER DEFAULT 0," \
" rating INTEGER DEFAULT 0," \
" play_count INTEGER DEFAULT 0," \
" skip_count INTEGER DEFAULT 0," \
" seek INTEGER DEFAULT 0," \
" data_kind INTEGER DEFAULT 0," \
" media_kind INTEGER DEFAULT 0," \
" item_kind INTEGER DEFAULT 0," \
" description INTEGER DEFAULT 0," \
" db_timestamp INTEGER DEFAULT 0," \
" time_added INTEGER DEFAULT 0," \
" time_modified INTEGER DEFAULT 0," \
" time_played INTEGER DEFAULT 0," \
" db_timestamp INTEGER DEFAULT 0," \
" time_skipped INTEGER DEFAULT 0," \
" disabled INTEGER DEFAULT 0," \
" sample_count INTEGER DEFAULT 0," \
" codectype VARCHAR(5) DEFAULT NULL," \
@ -77,25 +84,18 @@
" has_video INTEGER DEFAULT 0," \
" contentrating INTEGER DEFAULT 0," \
" bits_per_sample INTEGER DEFAULT 0," \
" album_artist VARCHAR(1024) NOT NULL COLLATE DAAP," \
" media_kind INTEGER NOT NULL," \
" tv_series_name VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
" tv_episode_num_str VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
" tv_network_name VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
" tv_episode_sort INTEGER NOT NULL," \
" tv_season_num INTEGER NOT NULL," \
" songartistid INTEGER NOT NULL," \
" songalbumid INTEGER NOT NULL," \
" songartistid INTEGER DEFAULT 0," \
" songalbumid INTEGER DEFAULT 0," \
" title_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
" artist_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
" album_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
" composer_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
" album_artist_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP," \
" virtual_path VARCHAR(4096) DEFAULT NULL," \
" directory_id INTEGER DEFAULT 0," \
" date_released INTEGER DEFAULT 0," \
" skip_count INTEGER DEFAULT 0," \
" time_skipped INTEGER DEFAULT 0" \
" composer_sort VARCHAR(1024) DEFAULT NULL COLLATE DAAP" \
");"
#define T_PL \
@ -194,20 +194,6 @@
" composer VARCHAR(1024) DEFAULT NULL" \
");"
#define TRG_GROUPS_INSERT_FILES \
"CREATE TRIGGER update_groups_new_file AFTER INSERT ON files FOR EACH ROW" \
" BEGIN" \
" INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (1, NEW.album, NEW.songalbumid);" \
" INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (2, NEW.album_artist, NEW.songartistid);" \
" END;"
#define TRG_GROUPS_UPDATE_FILES \
"CREATE TRIGGER update_groups_update_file AFTER UPDATE OF songalbumid ON files FOR EACH ROW" \
" BEGIN" \
" INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (1, NEW.album, NEW.songalbumid);" \
" INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (2, NEW.album_artist, NEW.songartistid);" \
" END;"
#define Q_PL1 \
"INSERT INTO playlists (id, title, type, query, db_timestamp, path, idx, special_id)" \
" VALUES(1, 'Library', 0, '1 = 1', 0, '', 0, 0);"
@ -278,19 +264,18 @@ static const struct db_init_query db_init_table_queries[] =
{ T_DIRECTORIES, "create table directories" },
{ T_QUEUE, "create table queue" },
{ TRG_GROUPS_INSERT_FILES, "create trigger update_groups_new_file" },
{ TRG_GROUPS_UPDATE_FILES, "create trigger update_groups_update_file" },
{ Q_PL1, "create default playlist" },
{ Q_PL2, "create default smart playlist 'Music'" },
{ Q_PL3, "create default smart playlist 'Movies'" },
{ Q_PL4, "create default smart playlist 'TV Shows'" },
{ Q_PL5, "create default smart playlist 'Podcasts'" },
{ Q_PL6, "create default smart playlist 'Audiobooks'" },
{ Q_DIR1, "create default root directory '/'" },
{ Q_DIR2, "create default base directory '/file:'" },
{ Q_DIR3, "create default base directory '/http:'" },
{ Q_DIR4, "create default base directory '/spotify:'" },
{ Q_QUEUE_VERSION, "initialize queue version" },
};
@ -411,6 +396,41 @@ static const struct db_init_query db_init_index_queries[] =
{ I_QUEUE_SHUFFLEPOS, "create queue shuffle pos index" },
};
/* Triggers must be prefixed with trg_ for db_drop_triggers() to id them */
#define TRG_FILES_INSERT_SONGIDS \
"CREATE TRIGGER trg_files_insert_songids AFTER INSERT ON files FOR EACH ROW" \
" BEGIN" \
" UPDATE files SET songartistid = daap_songalbumid(LOWER(NEW.album_artist), ''), " \
" songalbumid = daap_songalbumid(LOWER(NEW.album_artist), LOWER(NEW.album))" \
" WHERE id = NEW.id;" \
" END;"
#define TRG_FILES_UPDATE_SONGIDS \
"CREATE TRIGGER trg_files_update_songids AFTER UPDATE OF album_artist, album ON files FOR EACH ROW" \
" BEGIN" \
" UPDATE files SET songartistid = daap_songalbumid(LOWER(NEW.album_artist), ''), " \
" songalbumid = daap_songalbumid(LOWER(NEW.album_artist), LOWER(NEW.album))" \
" WHERE id = NEW.id;" \
" END;"
#define TRG_GROUPS_UPDATE \
"CREATE TRIGGER trg_groups_update AFTER UPDATE OF songartistid, songalbumid ON files FOR EACH ROW" \
" WHEN (NEW.songartistid != 0 AND NEW.songalbumid != 0)" \
" BEGIN" \
" INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (1, NEW.album, NEW.songalbumid);" \
" INSERT OR IGNORE INTO groups (type, name, persistentid) VALUES (2, NEW.album_artist, NEW.songartistid);" \
" END;"
static const struct db_init_query db_init_trigger_queries[] =
{
{ TRG_FILES_INSERT_SONGIDS, "create trigger trg_files_insert_songids" },
{ TRG_FILES_UPDATE_SONGIDS, "create trigger trg_files_update_songids" },
{ TRG_GROUPS_UPDATE, "create trigger trg_groups_update" },
};
int
db_init_indices(sqlite3 *hdl)
{
@ -435,6 +455,30 @@ db_init_indices(sqlite3 *hdl)
return 0;
}
int
db_init_triggers(sqlite3 *hdl)
{
char *errmsg;
int i;
int ret;
for (i = 0; i < (sizeof(db_init_trigger_queries) / sizeof(db_init_trigger_queries[0])); i++)
{
DPRINTF(E_DBG, L_DB, "DB init trigger query: %s\n", db_init_trigger_queries[i].desc);
ret = sqlite3_exec(hdl, db_init_trigger_queries[i].query, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
DPRINTF(E_FATAL, L_DB, "DB init error: %s\n", errmsg);
sqlite3_free(errmsg);
return -1;
}
}
return 0;
}
int
db_init_tables(sqlite3 *hdl)
{

View File

@ -25,12 +25,15 @@
* version of the database? If yes, then it is a minor upgrade, if no, then it
* is a major upgrade. In other words minor version upgrades permit downgrading
* forked-daapd after the database was upgraded. */
#define SCHEMA_VERSION_MAJOR 19
#define SCHEMA_VERSION_MINOR 12
#define SCHEMA_VERSION_MAJOR 20
#define SCHEMA_VERSION_MINOR 00
int
db_init_indices(sqlite3 *hdl);
int
db_init_triggers(sqlite3 *hdl);
int
db_init_tables(sqlite3 *hdl);

File diff suppressed because it is too large Load Diff

View File

@ -154,7 +154,7 @@ response_process(struct http_client_ctx *ctx, char **errmsg)
DPRINTF(E_DBG, L_LASTFM, "LastFM response:\n%s\n", body);
if (errmsg)
*errmsg = trimwhitespace(mxmlGetOpaque(e_node));
*errmsg = atrim(mxmlGetOpaque(e_node));
mxmlDelete(tree);
return -1;
@ -180,7 +180,7 @@ response_process(struct http_client_ctx *ctx, char **errmsg)
return -1;
}
sk = trimwhitespace(mxmlGetOpaque(s_node));
sk = atrim(mxmlGetOpaque(s_node));
if (sk)
{
DPRINTF(E_LOG, L_LASTFM, "Got session key from LastFM: %s\n", sk);

View File

@ -128,14 +128,6 @@ library_add_media(struct media_file_info *mfi)
mfi->path, mfi->directory_id, mfi->virtual_path);
}
if (!mfi->item_kind)
mfi->item_kind = 2; /* music */
if (!mfi->media_kind)
mfi->media_kind = MEDIA_KIND_MUSIC; /* music */
unicode_fixup_mfi(mfi);
fixup_tags_mfi(mfi);
if (mfi->id == 0)
db_file_add(mfi);
else

View File

@ -509,14 +509,18 @@ process_regular_file(const char *file, struct stat *sb, int type, int flags, int
else
{
mfi.data_kind = DATA_KIND_FILE;
mfi.file_size = sb->st_size;
if (type & F_SCAN_TYPE_AUDIOBOOK)
mfi.media_kind = MEDIA_KIND_AUDIOBOOK;
else if (type & F_SCAN_TYPE_PODCAST)
mfi.media_kind = MEDIA_KIND_PODCAST;
mfi.compilation = (type & F_SCAN_TYPE_COMPILATION);
mfi.file_size = sb->st_size;
if (type & F_SCAN_TYPE_COMPILATION)
{
mfi.compilation = 1;
mfi.album_artist = safe_strdup(cfg_getstr(cfg_getsec(cfg, "library"), "compilation_artist"));
}
ret = scan_metadata_ffmpeg(file, &mfi);
if (ret < 0)
@ -1686,7 +1690,6 @@ queue_add_stream(const char *path, int position, char reshuffle, uint32_t item_i
memset(&mfi, 0, sizeof(struct media_file_info));
scan_metadata_stream(path, &mfi);
unicode_fixup_mfi(&mfi);
map_media_file_to_queue_item(&item, &mfi);

View File

@ -548,7 +548,6 @@ process_track_file(plist_t trk)
mfi->album_artist = strdup(mfi->artist);
}
unicode_fixup_mfi(mfi);
db_file_update(mfi);
free_mfi(mfi, 0);

View File

@ -33,6 +33,7 @@
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdarg.h>
#include <limits.h>
#include <sys/param.h>
#ifndef CLOCK_REALTIME
@ -341,6 +342,7 @@ safe_asprintf(const char *fmt, ...)
{
char *ret = NULL;
va_list va;
va_start(va, fmt);
if (vasprintf(&ret, fmt, va) < 0)
{
@ -348,9 +350,34 @@ safe_asprintf(const char *fmt, ...)
abort();
}
va_end(va);
return ret;
}
int
safe_snprintf_cat(char *dst, size_t n, const char *fmt, ...)
{
size_t dstlen;
va_list va;
int ret;
if (!dst || !fmt)
return -1;
dstlen = strlen(dst);
if (n < dstlen)
return -1;
va_start(va, fmt);
ret = vsnprintf(dst + dstlen, n - dstlen, fmt, va);
va_end(va);
if (ret >= 0 && ret < n - dstlen)
return 0;
else
return -1;
}
/* Key/value functions */
struct keyval *
@ -586,7 +613,7 @@ m_readfile(const char *path, int num_lines)
goto error;
}
lines[i] = trimwhitespace(line);
lines[i] = atrim(line);
if (!lines[i] || (strlen(lines[i]) == 0))
{
DPRINTF(E_LOG, L_MISC, "Line %d in '%s' is invalid\n", i+1, path);
@ -643,40 +670,58 @@ unicode_fixup_string(char *str, const char *fromcode)
}
char *
trimwhitespace(const char *str)
trim(char *str)
{
char *ptr;
char *start;
char *out;
size_t start; // Position of first non-space char
size_t term; // Position of 0-terminator
if (!str)
return NULL;
// Find the beginning
while (isspace(*str))
str++;
start = 0;
term = strlen(str);
if (*str == 0) // All spaces?
return strdup("");
while ((start < term) && isspace(str[start]))
start++;
while ((term > start) && isspace(str[term - 1]))
term--;
// Make copy, because we will need to insert a null terminator
start = strdup(str);
if (!start)
str[term] = '\0';
// Shift chars incl. terminator
if (start)
memmove(str, str + start, term - start + 1);
return str;
}
char *
atrim(const char *str)
{
size_t start; // Position of first non-space char
size_t term; // Position of 0-terminator
size_t size;
char *result;
if (!str)
return NULL;
// Find the end
ptr = start + strlen(start) - 1;
while (ptr > start && isspace(*ptr))
ptr--;
start = 0;
term = strlen(str);
// Insert null terminator
*(ptr+1) = 0;
while ((start < term) && isspace(str[start]))
start++;
while ((term > start) && isspace(str[term - 1]))
term--;
out = strdup(start);
size = term - start + 1;
free(start);
result = malloc(size);
return out;
memcpy(result, str + start, size);
result[size - 1] = '\0';
return result;
}
void

View File

@ -58,6 +58,10 @@ safe_strdup(const char *str);
char *
safe_asprintf(const char *fmt, ...);
int
safe_snprintf_cat(char *dst, size_t n, const char *fmt, ...);
/* Key/value functions */
struct keyval *
keyval_alloc(void);
@ -87,8 +91,13 @@ m_readfile(const char *path, int num_lines);
char *
unicode_fixup_string(char *str, const char *fromcode);
// Modifies str so it is trimmed. Returns pointer to str.
char *
trimwhitespace(const char *str);
trim(char *str);
// Copies the trimmed part of str to a newly allocated string (caller must free)
char *
atrim(const char *str);
void
swap_pointers(char **a, char **b);
@ -105,8 +114,7 @@ b64_encode(const uint8_t *in, size_t len);
uint64_t
murmur_hash64(const void *key, int len, uint32_t seed);
/* Checks if the address is in a network that is configured as trusted */
// Checks if the address is in a network that is configured as trusted
bool
peer_address_is_trusted(const char *addr);