2 * This file is part of DisOrder
3 * Copyright (C) 2007 Richard Kettlewell
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
38 #include "trackdb-int.h"
40 #include "configuration.h"
43 static DB_TXN *global_tid;
45 static const struct option options[] = {
46 { "help", no_argument, 0, 'h' },
47 { "version", no_argument, 0, 'V' },
48 { "config", required_argument, 0, 'c' },
49 { "debug", no_argument, 0, 'd' },
50 { "no-debug", no_argument, 0, 'D' },
51 { "syslog", no_argument, 0, 's' },
52 { "no-syslog", no_argument, 0, 'S' },
56 /* display usage message and terminate */
57 static void help(void) {
59 " disorder-dbupgrade [OPTIONS]\n"
61 " --help, -h Display usage message\n"
62 " --version, -V Display version number\n"
63 " --config PATH, -c PATH Set configuration file\n"
64 " --debug, -d Turn on debugging\n"
65 " --[no-]syslog Force logging\n"
67 "Database upgrader for DisOrder. Not intended to be run\n"
73 /* display version number and terminate */
74 static void version(void) {
75 xprintf("disorder-dbupgrade version %s\n", disorder_version_string);
80 /** @brief Visit each key in a database and call @p callback
81 * @return 0 or DB_LOCK_DEADLOCK
83 * @p global_tid must be set. @p callback should return 0 or DB_LOCK_DEADLOCK.
85 static int scan_core(const char *name, DB *db,
86 int (*callback)(const char *name, DB *db, DBC *c,
89 DBC *c = trackdb_opencursor(db, global_tid);
93 memset(k, 0, sizeof k);
94 memset(d, 0, sizeof d);
95 while((err = c->c_get(c, k, d, DB_NEXT)) == 0) {
96 if((err = callback(name, db, c, k, d)))
100 info("scanning %s, %ld so far", name, count);
102 if(err && err != DB_NOTFOUND && err != DB_LOCK_DEADLOCK)
103 fatal(0, "%s: error scanning database: %s", name, db_strerror(err));
104 r = (err == DB_LOCK_DEADLOCK ? err : 0);
105 if((err = c->c_close(c)))
106 fatal(0, "%s: error closing cursor: %s", name, db_strerror(err));
110 /** @brief Visit each key in a database and call @p callback
112 * Everything happens inside the @p global_tid tranasction. @p callback
113 * should return 0 or DB_LOCK_DEADLOCK.
115 static void scan(const char *name, DB *db,
116 int (*callback)(const char *name, DB *db, DBC *c,
118 info("scanning %s", name);
120 global_tid = trackdb_begin_transaction();
121 if(scan_core(name, db, callback)) {
122 trackdb_abort_transaction(global_tid);
124 error(0, "detected deadlock, restarting scan");
127 trackdb_commit_transaction(global_tid);
134 /** @brief Truncate database @p db */
135 static void truncate_database(const char *name, DB *db) {
140 err = db->truncate(db, 0, &count, DB_AUTO_COMMIT);
141 } while(err == DB_LOCK_DEADLOCK);
143 fatal(0, "error truncating %s: %s", name, db_strerror(err));
148 static int normalize_keys(const char *name, DB *db, DBC *c,
154 /* Find the normalized form of the key */
155 knfc = utf8_compose_canon(k->data, k->size, &nknfc);
157 fatal(0, "%s: cannot convert key to NFC: %.*s", name,
158 (int)k->size, (const char *)k->data);
159 /* If the key is already in NFC then do nothing */
160 if(nknfc == k->size && !memcmp(k->data, knfc, nknfc))
162 /* To rename the key we must delete the old one and insert a new one */
163 if((err = c->c_del(c, 0))) {
164 if(err != DB_LOCK_DEADLOCK)
165 fatal(0, "%s: error removing denormalized key: %s",
166 name, db_strerror(err));
171 if((err = db->put(db, global_tid, k, d, DB_NOOVERWRITE))) {
172 if(err != DB_LOCK_DEADLOCK)
173 fatal(0, "%s: error storing normalized key: %s", name, db_strerror(err));
179 static int normalize_values(const char *name, DB *db,
180 DBC attribute((unused)) *c,
186 /* Find the normalized form of the value */
187 dnfc = utf8_compose_canon(d->data, d->size, &ndnfc);
189 fatal(0, "%s: cannot convert data to NFC: %.*s", name,
190 (int)d->size, (const char *)d->data);
191 /* If the key is already in NFC then do nothing */
192 if(ndnfc == d->size && !memcmp(d->data, dnfc, ndnfc))
196 if((err = db->put(db, global_tid, k, d, 0))) {
197 if(err != DB_LOCK_DEADLOCK)
198 fatal(0, "%s: error storing normalized data: %s", name, db_strerror(err));
204 static int renotice(const char *name, DB attribute((unused)) *db,
205 DBC attribute((unused)) *c,
207 const struct kvp *const t = kvp_urldecode(d->data, d->size);
208 const char *const track = xstrndup(k->data, k->size);
209 const char *const path = kvp_get(t, "_path");
213 fatal(0, "%s: no '_path' for %.*s", name,
214 (int)k->size, (const char *)k->data);
215 switch(err = trackdb_notice_tid(track, path, global_tid)) {
218 case DB_LOCK_DEADLOCK:
221 fatal(0, "%s: unexpected return from trackdb_notice_tid: %s",
222 name, db_strerror(err));
226 static int remove_aliases_normalize_keys(const char *name, DB *db, DBC *c,
228 const struct kvp *const t = kvp_urldecode(d->data, d->size);
231 if(kvp_get(t, "_alias_for")) {
232 /* This is an alias. We remove all the alias entries. */
233 if((err = c->c_del(c, 0))) {
234 if(err != DB_LOCK_DEADLOCK)
235 fatal(0, "%s: error removing alias: %s", name, db_strerror(err));
240 return normalize_keys(name, db, c, k, d);
243 /** @brief Upgrade the database to the current version
245 * This function is supposed to be idempotent, so if it is interrupted
246 * half way through it is safe to restart.
248 static void upgrade(void) {
251 info("upgrading database to dbversion %ld", config->dbversion);
252 /* Normalize keys and values as required. We will also remove aliases as
253 * they will be regenerated when we re-noticed the tracks. */
254 info("renormalizing keys");
255 scan("tracks.db", trackdb_tracksdb, remove_aliases_normalize_keys);
256 scan("prefs.db", trackdb_prefsdb, normalize_keys);
257 scan("global.db", trackdb_globaldb, normalize_keys);
258 scan("noticed.db", trackdb_noticeddb, normalize_values);
259 /* search.db and tags.db we will rebuild */
260 info("regenerating search database and aliases");
261 truncate_database("search.db", trackdb_searchdb);
262 truncate_database("tags.db", trackdb_tagsdb);
263 /* Regenerate the search database and aliases */
264 scan("tracks.db", trackdb_tracksdb, renotice);
265 /* Finally update the database version */
266 snprintf(buf, sizeof buf, "%ld", config->dbversion);
267 trackdb_set_global("_dbversion", buf, 0);
268 info("completed database upgrade");
271 int main(int argc, char **argv) {
272 int n, logsyslog = !isatty(2);
276 if(!setlocale(LC_CTYPE, "")) fatal(errno, "error calling setlocale");
277 while((n = getopt_long(argc, argv, "hVc:dDSs", options, 0)) >= 0) {
281 case 'c': configfile = optarg; break;
282 case 'd': debugging = 1; break;
283 case 'D': debugging = 0; break;
284 case 'S': logsyslog = 0; break;
285 case 's': logsyslog = 1; break;
286 default: fatal(0, "invalid option");
289 /* If stderr is a TTY then log there, otherwise to syslog. */
291 openlog(progname, LOG_PID, LOG_DAEMON);
292 log_default = &log_syslog;
294 if(config_read(0)) fatal(0, "cannot read configuration");
295 /* Open the database */
296 trackdb_init(TRACKDB_NO_RECOVER);
297 trackdb_open(TRACKDB_OPEN_FOR_UPGRADE);