X-Git-Url: https://git.distorted.org.uk/~mdw/disorder/blobdiff_plain/05b75f8d50b83e943af3be4071449304d82dbdcd..2563dc1f46fad1640e2472151a9f1cf918f841b9:/lib/trackdb.c diff --git a/lib/trackdb.c b/lib/trackdb.c index 689af09..a364446 100644 --- a/lib/trackdb.c +++ b/lib/trackdb.c @@ -35,7 +35,6 @@ #include #include #include -#include #include #include #include @@ -61,6 +60,7 @@ #include "unicode.h" #include "unidata.h" #include "base64.h" +#include "sendmail.h" #define RESCAN "disorder-rescan" #define DEADLOCK "disorder-deadlock" @@ -159,6 +159,13 @@ DB *trackdb_scheduledb; */ DB *trackdb_usersdb; +/** @brief The playlists database + * - Keys are playlist names + * - Values are encoded key-value pairs + * - Data is user data and cannot be reconstructed + */ +DB *trackdb_playlistsdb; + static pid_t db_deadlock_pid = -1; /* deadlock manager PID */ static pid_t rescan_pid = -1; /* rescanner PID */ static int initialized, opened; /* state */ @@ -468,6 +475,7 @@ void trackdb_open(int flags) { trackdb_noticeddb = open_db("noticed.db", DB_DUPSORT, DB_BTREE, dbflags, 0666); trackdb_scheduledb = open_db("schedule.db", 0, DB_HASH, dbflags, 0666); + trackdb_playlistsdb = open_db("playlists.db", 0, DB_HASH, dbflags, 0666); if(!trackdb_existing_database) { /* Stash the database version */ char buf[32]; @@ -502,6 +510,8 @@ void trackdb_close(void) { fatal(0, "error closing schedule.db: %s", db_strerror(err)); if((err = trackdb_usersdb->close(trackdb_usersdb, 0))) fatal(0, "error closing users.db: %s", db_strerror(err)); + if((err = trackdb_playlistsdb->close(trackdb_playlistsdb, 0))) + fatal(0, "error closing playlists.db: %s", db_strerror(err)); trackdb_tracksdb = trackdb_searchdb = trackdb_prefsdb = 0; trackdb_tagsdb = trackdb_globaldb = 0; D(("closed databases")); @@ -509,8 +519,13 @@ void trackdb_close(void) { /* generic db routines *******************************************************/ -/* fetch and decode a database entry. Returns 0, DB_NOTFOUND or - * DB_LOCK_DEADLOCK. */ +/** @brief Fetch and decode a database entry + * @param db Database + * @param track Track name + * @param kp Where to put decoded list (or NULL if you don't care) + * @param tid Owning transaction + * @return 0, @c DB_NOTFOUND or @c DB_LOCK_DEADLOCK + */ int trackdb_getdata(DB *db, const char *track, struct kvp **kp, @@ -521,10 +536,12 @@ int trackdb_getdata(DB *db, switch(err = db->get(db, tid, make_key(&key, track), prepare_data(&data), 0)) { case 0: - *kp = kvp_urldecode(data.data, data.size); + if(kp) + *kp = kvp_urldecode(data.data, data.size); return 0; case DB_NOTFOUND: - *kp = 0; + if(kp) + *kp = 0; return err; case DB_LOCK_DEADLOCK: error(0, "error querying database: %s", db_strerror(err)); @@ -1853,7 +1870,7 @@ static int do_list(struct vector *v, const char *dir, char *ptr; int err; size_t l, last_dir_len = 0; - char *last_dir = 0, *track, *alias; + char *last_dir = 0, *track; struct kvp *p; dl = strlen(dir); @@ -1886,12 +1903,35 @@ static int do_list(struct vector *v, const char *dir, if((err = trackdb_getdata(trackdb_prefsdb, track, &p, tid)) == DB_LOCK_DEADLOCK) goto deadlocked; + /* There's an awkward question here... + * + * If a track shares a directory with its alias then we could + * do one of three things: + * - report both. Looks ridiculuous in most UIs. + * - report just the alias. Remarkably inconvenient to write + * UI code for! + * - report just the real name. Ugly if the UI doesn't prettify + * names via the name parts. + */ +#if 1 + /* If this file is an alias for a track in the same directory then we + * skip it */ + struct kvp *t = kvp_urldecode(d.data, d.size); + const char *alias_target = kvp_get(t, "_alias_for"); + if(!(alias_target + && !strcmp(d_dirname(alias_target), + d_dirname(track)))) + if(track_matches(dl, k.data, k.size, re)) + vector_append(v, track); +#else /* if this file has an alias in the same directory then we skip it */ + char *alias; if((err = compute_alias(&alias, track, p, tid))) goto deadlocked; if(!(alias && !strcmp(d_dirname(alias), d_dirname(track)))) if(track_matches(dl, k.data, k.size, re)) vector_append(v, track); +#endif } } err = cursor->c_get(cursor, &k, &d, DB_NEXT); @@ -2392,12 +2432,24 @@ static char **trackdb_new_tid(int *ntracksp, DBT k, d; int err = 0; struct vector tracks[1]; + hash *h = hash_new(1); vector_init(tracks); c = trackdb_opencursor(trackdb_noticeddb, tid); while((maxtracks <= 0 || tracks->nvec < maxtracks) - && !(err = c->c_get(c, prepare_data(&k), prepare_data(&d), DB_PREV))) - vector_append(tracks, xstrndup(d.data, d.size)); + && !(err = c->c_get(c, prepare_data(&k), prepare_data(&d), DB_PREV))) { + char *const track = xstrndup(d.data, d.size); + /* Don't add any track more than once */ + if(hash_add(h, track, "", HASH_INSERT)) + continue; + /* See if the track still exists */ + err = trackdb_getdata(trackdb_tracksdb, track, NULL/*kp*/, tid); + if(err == DB_NOTFOUND) + continue; /* It doesn't, skip it */ + if(err == DB_LOCK_DEADLOCK) + break; /* Doh */ + vector_append(tracks, track); + } switch(err) { case 0: /* hit maxtracks */ case DB_NOTFOUND: /* ran out of tracks */ @@ -2509,8 +2561,10 @@ static int trusted(const char *user) { * Currently we only allow the letters and digits in ASCII. We could be more * liberal than this but it is a nice simple test. It is critical that * semicolons are never allowed. + * + * NB also used by playlist_parse_name() to validate playlist names! */ -static int valid_username(const char *user) { +int valid_username(const char *user) { if(!*user) return 0; while(*user) { @@ -2679,6 +2733,7 @@ int trackdb_adduser(const char *user, user, rights, email); else info("created user '%s' with rights '%s'", user, rights); + eventlog("user_add", user, (char *)0); return 0; } } @@ -2696,6 +2751,7 @@ int trackdb_deluser(const char *user) { return -1; } info("deleted user '%s'", user); + eventlog("user_delete", user, (char *)0); return 0; } @@ -2757,8 +2813,8 @@ int trackdb_edituserinfo(const char *user, } } else if(!strcmp(key, "email")) { if(*value) { - if(!strchr(value, '@')) { - error(0, "invalid email address '%s' for user '%s'", user, value); + if(!email_valid(value)) { + error(0, "invalid email address '%s' for user '%s'", value, user); return -1; } } else @@ -2775,8 +2831,10 @@ int trackdb_edituserinfo(const char *user, if(e) { error(0, "unknown user '%s'", user); return -1; - } else + } else { + eventlog("user_edit", user, key, (char *)0); return 0; + } } /** @brief List all users @@ -2842,6 +2900,7 @@ int trackdb_confirm(const char *user, const char *confirmation, switch(e) { case 0: info("registration confirmed for user '%s'", user); + eventlog("user_confirm", user, (char *)0); return 0; case DB_NOTFOUND: error(0, "confirmation for nonexistent user '%s'", user);