X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/b9bbb6c8bd8630fe4321045ce57174c4890bfa46..985bb670b4e07d35cb1580780253ded2524a342e:/lib/trackdb.c
diff --git a/lib/trackdb.c b/lib/trackdb.c
index 7cc3719..e1bbfc8 100644
--- a/lib/trackdb.c
+++ b/lib/trackdb.c
@@ -2,20 +2,18 @@
* This file is part of DisOrder
* Copyright (C) 2005-2008 Richard Kettlewell
*
- * This program is free software; you can redistribute it and/or modify
+ * 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 of the License, or
+ * the Free Software Foundation, either version 3 of the License, 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.
- *
+ *
+ * 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
+ * along with this program. If not, see .
*/
/** @file lib/trackdb.c
* @brief Track database
@@ -23,15 +21,11 @@
* This file is getting in desparate need of splitting up...
*/
-#include
-#include "types.h"
+#include "common.h"
-#include
-#include
#include
#include
#include
-#include
#include
#include
#include
@@ -39,7 +33,6 @@
#include
#include
#include
-#include
#include
#include
#include
@@ -65,6 +58,7 @@
#include "unicode.h"
#include "unidata.h"
#include "base64.h"
+#include "sendmail.h"
#define RESCAN "disorder-rescan"
#define DEADLOCK "disorder-deadlock"
@@ -360,7 +354,7 @@ static DB *open_db(const char *path,
DBTYPE dbtype,
u_int32_t openflags,
int mode) {
- int err;
+ int err, err2;
DB *db;
D(("open %s", path));
@@ -375,8 +369,14 @@ static DB *open_db(const char *path,
fatal(0, "db->set_bt_compare %s: %s", path, db_strerror(err));
if((err = db->open(db, 0, path, 0, dbtype,
openflags | DB_AUTO_COMMIT, mode))) {
- if((openflags & DB_CREATE) || errno != ENOENT)
+ if((openflags & DB_CREATE) || errno != ENOENT) {
+ if((err2 = db->close(db, 0)))
+ error(0, "db->close: %s", db_strerror(err2));
+ trackdb_close();
+ trackdb_env->close(trackdb_env,0);
+ trackdb_env = 0;
fatal(0, "db->open %s: %s", path, db_strerror(err));
+ }
db->close(db, 0);
db = 0;
}
@@ -490,31 +490,31 @@ void trackdb_close(void) {
/* sanity checks */
assert(opened == 1);
--opened;
- if((err = trackdb_tracksdb->close(trackdb_tracksdb, 0)))
- fatal(0, "error closing tracks.db: %s", db_strerror(err));
- if((err = trackdb_searchdb->close(trackdb_searchdb, 0)))
- fatal(0, "error closing search.db: %s", db_strerror(err));
- if((err = trackdb_tagsdb->close(trackdb_tagsdb, 0)))
- fatal(0, "error closing tags.db: %s", db_strerror(err));
- if((err = trackdb_prefsdb->close(trackdb_prefsdb, 0)))
- fatal(0, "error closing prefs.db: %s", db_strerror(err));
- if((err = trackdb_globaldb->close(trackdb_globaldb, 0)))
- fatal(0, "error closing global.db: %s", db_strerror(err));
- if((err = trackdb_noticeddb->close(trackdb_noticeddb, 0)))
- fatal(0, "error closing noticed.db: %s", db_strerror(err));
- if((err = trackdb_scheduledb->close(trackdb_scheduledb, 0)))
- 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));
- trackdb_tracksdb = trackdb_searchdb = trackdb_prefsdb = 0;
- trackdb_tagsdb = trackdb_globaldb = 0;
+#define CLOSE(N, V) do { \
+ if(V && (err = V->close(V, 0))) \
+ fatal(0, "error closing %s: %s", N, db_strerror(err)); \
+ V = 0; \
+} while(0)
+ CLOSE("tracks.db", trackdb_tracksdb);
+ CLOSE("search.db", trackdb_searchdb);
+ CLOSE("tags.db", trackdb_tagsdb);
+ CLOSE("prefs.db", trackdb_prefsdb);
+ CLOSE("global.db", trackdb_globaldb);
+ CLOSE("noticed.db", trackdb_noticeddb);
+ CLOSE("schedule.db", trackdb_scheduledb);
+ CLOSE("users.db", trackdb_usersdb);
D(("closed databases"));
}
/* 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,
@@ -525,10 +525,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));
@@ -1398,7 +1400,9 @@ void trackdb_stats_subprocess(ev_source *ev,
pid = subprogram(ev, p[1], "disorder-stats", (char *)0);
xclose(p[1]);
ev_child(ev, pid, 0, stats_finished, d);
- ev_reader_new(ev, p[0], stats_read, stats_error, d, "disorder-stats reader");
+ if(!ev_reader_new(ev, p[0], stats_read, stats_error, d,
+ "disorder-stats reader"))
+ fatal(0, "ev_reader_new for disorder-stats reader failed");
}
/** @brief Parse a track name part preference
@@ -1754,8 +1758,9 @@ int trackdb_request_random(ev_source *ev,
choose_callback = callback;
choose_output.nvec = 0;
choose_complete = 0;
- ev_reader_new(ev, p[0], choose_readable, choose_read_error, 0,
- "disorder-choose reader"); /* owns p[0] */
+ if(!ev_reader_new(ev, p[0], choose_readable, choose_read_error, 0,
+ "disorder-choose reader")) /* owns p[0] */
+ fatal(0, "ev_reader_new for disorder-choose reader failed");
ev_child(ev, choose_pid, 0, choose_exited, 0); /* owns the subprocess */
return 0;
}
@@ -1857,7 +1862,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);
@@ -1890,12 +1895,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);
@@ -2209,7 +2237,7 @@ static int reap_rescan(ev_source attribute((unused)) *ev,
eventlog("rescanned", (char *)0);
/* Call rescanned callbacks */
while(rescanned_list) {
- void (*rescanned)(void *u) = rescanned_list->rescanned;
+ void (*rescanned)(void *u_) = rescanned_list->rescanned;
void *ru = rescanned_list->ru;
rescanned_list = rescanned_list->next;
@@ -2222,7 +2250,7 @@ static int reap_rescan(ev_source attribute((unused)) *ev,
* @param ev Event loop or 0 to block
* @param recheck 1 to recheck lengths, 0 to suppress check
* @param rescanned Called on completion (if not NULL)
- * @param u Passed to @p rescanned
+ * @param ru Passed to @p rescanned
*/
void trackdb_rescan(ev_source *ev, int recheck,
void (*rescanned)(void *ru),
@@ -2396,12 +2424,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 */
@@ -2683,6 +2723,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;
}
}
@@ -2700,6 +2741,7 @@ int trackdb_deluser(const char *user) {
return -1;
}
info("deleted user '%s'", user);
+ eventlog("user_delete", user, (char *)0);
return 0;
}
@@ -2761,8 +2803,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
@@ -2779,8 +2821,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
@@ -2846,6 +2890,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);