Implement lms_check()
authorGustavo Sverzut Barbieri <barbieri@gmail.com>
Sun, 9 Dec 2007 02:19:26 +0000 (23:19 -0300)
committerGustavo Sverzut Barbieri <barbieri@gmail.com>
Sun, 9 Dec 2007 02:19:26 +0000 (23:19 -0300)
This function is used to check files previously processed by
lms_process(), it will walk the registered files and stat(2) to see
current status, if required it will reparse files that changed.

src/bin/test.c
src/lib/Makefile.am
src/lib/lightmediascanner.h
src/lib/lightmediascanner_check.c [new file with mode: 0644]

index dad2d64..cb7ab52 100644 (file)
@@ -62,15 +62,21 @@ main(int argc, char *argv[])
         return -3;
     }
 
+    if (lms_check(lms, scan_path) != 0) {
+        fprintf(stderr, "ERROR: checking \"%s\".\n", scan_path);
+        lms_free(lms);
+        return -4;
+    }
+
     if (lms_process(lms, scan_path) != 0) {
         fprintf(stderr, "ERROR: processing \"%s\".\n", scan_path);
         lms_free(lms);
-        return -4;
+        return -5;
     }
 
     if (lms_free(lms) != 0) {
         fprintf(stderr, "ERROR: could not close light media scanner.\n");
-        return -5;
+        return -6;
     }
 
     return 0;
index f6d9b8b..6c30db5 100644 (file)
@@ -17,6 +17,7 @@ liblightmediascanner_la_SOURCES = \
        lightmediascanner_utils.c \
        lightmediascanner_charset_conv.c \
        lightmediascanner_process.c \
+       lightmediascanner_check.c \
        lightmediascanner_db_common.c \
        lightmediascanner_db_image.c \
        lightmediascanner_db_audio.c \
index 203d8ef..19cb15c 100644 (file)
@@ -72,6 +72,7 @@ extern "C" {
     API lms_t *lms_new(const char *db_path) GNUC_MALLOC GNUC_WARN_UNUSED_RESULT;
     API int lms_free(lms_t *lms) GNUC_NON_NULL(1);
     API int lms_process(lms_t *lms, const char *top_path) GNUC_NON_NULL(1, 2);
+    API int lms_check(lms_t *lms, const char *top_path) GNUC_NON_NULL(1, 2);
     API const char *lms_get_db_path(const lms_t *lms) GNUC_NON_NULL(1);
     API int lms_is_processing(const lms_t *lms) GNUC_PURE GNUC_NON_NULL(1);
     API int lms_get_slave_timeout(const lms_t *lms) GNUC_NON_NULL(1);
diff --git a/src/lib/lightmediascanner_check.c b/src/lib/lightmediascanner_check.c
new file mode 100644 (file)
index 0000000..ddf4139
--- /dev/null
@@ -0,0 +1,655 @@
+/**
+ * Copyright (C) 2007 by INdT
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2
+ * 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.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *
+ * @author Gustavo Sverzut Barbieri <gustavo.barbieri@openbossa.org>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#define _GNU_SOURCE
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <signal.h>
+#include <time.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lightmediascanner.h"
+#include "lightmediascanner_private.h"
+#include "lightmediascanner_db_private.h"
+
+struct master_db {
+    sqlite3 *handle;
+    sqlite3_stmt *get_files;
+};
+
+struct slave_db {
+    sqlite3 *handle;
+    sqlite3_stmt *transaction_begin;
+    sqlite3_stmt *transaction_commit;
+    sqlite3_stmt *delete_file_info;
+    sqlite3_stmt *update_file_info;
+};
+
+
+/***********************************************************************
+ * Master-Slave communication.
+ ***********************************************************************/
+
+struct comm_finfo {
+    int path_len;
+    int base;
+    int64_t id;
+    time_t mtime;
+    time_t dtime;
+    size_t size;
+    unsigned int flags;
+#define COMM_FINFO_FLAG_OUTDATED 1
+};
+
+static int
+_master_send_file(const struct fds *master, const struct lms_file_info finfo, unsigned int flags)
+{
+    struct comm_finfo ci;
+
+    ci.path_len = finfo.path_len;
+    ci.base = finfo.base;
+    ci.id = finfo.id;
+    ci.mtime = finfo.mtime;
+    ci.dtime = finfo.dtime;
+    ci.size = finfo.size;
+    ci.flags = flags;
+
+    if (write(master->w, &ci, sizeof(ci)) < 0) {
+        perror("write");
+        return -1;
+    }
+
+    if (write(master->w, finfo.path, finfo.path_len) < 0) {
+        perror("write");
+        return -1;
+    }
+
+    return 0;
+}
+
+static int
+_master_send_finish(const struct fds *master)
+{
+    struct comm_finfo ci = {-1, -1, -1, -1, -1, -1, 0};
+
+    if (write(master->w, &ci, sizeof(ci)) < 0) {
+        perror("write");
+        return -1;
+    }
+
+    return 0;
+}
+
+static int
+_master_recv_reply(const struct fds *master, struct pollfd *pfd, int *reply, int timeout)
+{
+    int r;
+
+    r = poll(pfd, 1, timeout);
+    if (r < 0) {
+        perror("poll");
+        return -1;
+    }
+
+    if (r == 0)
+        return 1;
+
+    if (read(master->r, reply, sizeof(*reply)) != sizeof(*reply)) {
+        perror("read");
+        return -2;
+    }
+
+    return 0;
+}
+
+static int
+_slave_send_reply(const struct fds *slave, int reply)
+{
+    if (write(slave->w, &reply, sizeof(reply)) == 0) {
+        perror("write");
+        return -1;
+    }
+    return 0;
+}
+
+static int
+_slave_recv_file(const struct fds *slave, struct lms_file_info *finfo, unsigned int *flags)
+{
+    struct comm_finfo ci;
+    static char path[PATH_SIZE + 1];
+    int r;
+
+    r = read(slave->r, &ci, sizeof(ci));
+    if (r != sizeof(ci)) {
+        perror("read");
+        return -1;
+    }
+
+    finfo->path_len = ci.path_len;
+    finfo->base = ci.base;
+    finfo->id = ci.id;
+    finfo->mtime = ci.mtime;
+    finfo->dtime = ci.dtime;
+    finfo->size = ci.size;
+    finfo->path = NULL;
+    *flags = ci.flags;
+
+    if (ci.path_len == -1)
+        return 0;
+
+    if (ci.path_len > PATH_SIZE) {
+        fprintf(stderr, "ERROR: path too long (%d/%d)\n",
+                ci.path_len, PATH_SIZE);
+        return -2;
+    }
+
+    r = read(slave->r, path, ci.path_len);
+    if (r != ci.path_len) {
+        fprintf(stderr, "ERROR: could not read whole path %d/%d\n",
+                r, ci.path_len);
+        return -3;
+    }
+
+    path[ci.path_len] = 0;
+    finfo->path = path;
+    return 0;
+}
+
+
+/***********************************************************************
+ * Slave-side.
+ ***********************************************************************/
+
+static int
+_slave_db_compile_all_stmts(struct slave_db *db)
+{
+    sqlite3 *handle;
+
+    handle = db->handle;
+
+    db->transaction_begin = lms_db_compile_stmt_begin_transaction(handle);
+    if (!db->transaction_begin)
+        return -1;
+
+    db->transaction_commit = lms_db_compile_stmt_end_transaction(handle);
+    if (!db->transaction_commit)
+        return -2;
+
+    db->delete_file_info = lms_db_compile_stmt_delete_file_info(handle);
+    if (!db->delete_file_info)
+        return -3;
+
+    db->update_file_info = lms_db_compile_stmt_update_file_info(handle);
+    if (!db->update_file_info)
+        return -4;
+
+    return 0;
+}
+
+static struct slave_db *
+_slave_db_open(const char *db_path)
+{
+    struct slave_db *db;
+
+    db = calloc(1, sizeof(*db));
+    if (!db) {
+        perror("calloc");
+        return NULL;
+    }
+
+    if (sqlite3_open(db_path, &db->handle) != SQLITE_OK) {
+        fprintf(stderr, "ERROR: could not open DB \"%s\": %s\n",
+                db_path, sqlite3_errmsg(db->handle));
+        goto error;
+    }
+
+    return db;
+
+  error:
+    sqlite3_close(db->handle);
+    free(db);
+    return NULL;
+}
+
+static int
+_slave_db_close(struct slave_db *db)
+{
+    if (db->transaction_begin)
+        lms_db_finalize_stmt(db->transaction_begin, "transaction_begin");
+
+    if (db->transaction_commit)
+        lms_db_finalize_stmt(db->transaction_commit, "transaction_commit");
+
+    if (db->delete_file_info)
+        lms_db_finalize_stmt(db->delete_file_info, "delete_file_info");
+
+    if (db->update_file_info)
+        lms_db_finalize_stmt(db->update_file_info, "update_file_info");
+
+    if (sqlite3_close(db->handle) != SQLITE_OK) {
+        fprintf(stderr, "ERROR: clould not close DB (slave): %s\n",
+                sqlite3_errmsg(db->handle));
+        return -1;
+    }
+    free(db);
+
+    return 0;
+}
+
+static int
+_init_sync_send(struct fds *fds)
+{
+    return _slave_send_reply(fds, 0);
+}
+
+static int
+_slave_work_int(lms_t *lms, struct fds *fds, struct slave_db *db)
+{
+    struct lms_file_info finfo;
+    void **parser_match;
+    unsigned int counter, flags;
+    int r;
+
+    parser_match = malloc(lms->n_parsers * sizeof(*parser_match));
+    if (!parser_match) {
+        perror("malloc");
+        return -6;
+    }
+
+    _init_sync_send(fds);
+
+    counter = 0;
+    lms_db_begin_transaction(db->transaction_begin);
+
+    while (((r = _slave_recv_file(fds, &finfo, &flags)) == 0) &&
+           finfo.path_len > 0) {
+        r = lms_db_update_file_info(db->update_file_info, &finfo);
+        if (r < 0)
+            fprintf(stderr, "ERROR: could not update path in DB\n");
+        else if (flags & COMM_FINFO_FLAG_OUTDATED) {
+            int used;
+
+            used = lms_parsers_check_using(lms, parser_match, &finfo);
+            if (!used)
+                r = 0;
+            else {
+                r = lms_parsers_run(lms, db->handle, parser_match, &finfo);
+                if (r < 0) {
+                    fprintf(stderr, "ERROR: pid=%d failed to parse \"%s\".\n",
+                            getpid(), finfo.path);
+                    lms_db_delete_file_info(db->delete_file_info, &finfo);
+                }
+            }
+        }
+
+        _slave_send_reply(fds, r);
+        counter++;
+        if (counter > lms->commit_interval) {
+            lms_db_end_transaction(db->transaction_commit);
+            lms_db_begin_transaction(db->transaction_begin);
+            counter = 0;
+        }
+    }
+
+    free(parser_match);
+    lms_db_end_transaction(db->transaction_commit);
+
+    return r;
+}
+
+static int
+_slave_work(lms_t *lms, struct fds *fds)
+{
+    struct slave_db *db;
+    int r;
+
+    db = _slave_db_open(lms->db_path);
+    if (!db)
+        return -1;
+
+    if (lms_parsers_setup(lms, db->handle) != 0) {
+        fprintf(stderr, "ERROR: could not setup parsers.\n");
+        r = -2;
+        goto end;
+    }
+
+    if (_slave_db_compile_all_stmts(db) != 0) {
+        fprintf(stderr, "ERROR: could not compile statements.\n");
+        r = -3;
+        goto end;
+    }
+
+    if (lms_parsers_start(lms, db->handle) != 0) {
+        fprintf(stderr, "ERROR: could not start parsers.\n");
+        r = -4;
+        goto end;
+    }
+    if (lms->n_parsers < 1) {
+        fprintf(stderr, "ERROR: no parser could be started, exit.\n");
+        r = -5;
+        goto end;
+    }
+
+    r = _slave_work_int(lms, fds, db);
+
+  end:
+    lms_parsers_finish(lms, db->handle);
+    _slave_db_close(db);
+    _init_sync_send(fds);
+
+    return r;
+}
+
+
+/***********************************************************************
+ * Master-side.
+ ***********************************************************************/
+
+static int
+_master_db_compile_all_stmts(struct master_db *db)
+{
+    sqlite3 *handle;
+
+    handle = db->handle;
+
+    db->get_files = lms_db_compile_stmt_get_files(handle);
+    if (!db->get_files)
+        return -1;
+
+    return 0;
+}
+
+static struct master_db *
+_master_db_open(const char *db_path)
+{
+    struct master_db *db;
+
+    db = calloc(1, sizeof(*db));
+    if (!db) {
+        perror("calloc");
+        return NULL;
+    }
+
+    if (sqlite3_open(db_path, &db->handle) != SQLITE_OK) {
+        fprintf(stderr, "ERROR: could not open DB \"%s\": %s\n",
+                db_path, sqlite3_errmsg(db->handle));
+        goto error;
+    }
+
+    if (lms_db_create_core_tables_if_required(db->handle) != 0) {
+        fprintf(stderr, "ERROR: could not setup tables and indexes.\n");
+        goto error;
+    }
+
+    if (_master_db_compile_all_stmts(db) != 0) {
+        fprintf(stderr, "ERROR: could not compile statements.\n");
+        goto error;
+    }
+
+    return db;
+
+  error:
+    sqlite3_close(db->handle);
+    free(db);
+    return NULL;
+}
+
+static int
+_master_db_close(struct master_db *db)
+{
+    if (db->get_files)
+        lms_db_finalize_stmt(db->get_files, "get_files");
+
+    if (sqlite3_close(db->handle) != SQLITE_OK) {
+        fprintf(stderr, "ERROR: clould not close DB (master): %s\n",
+                sqlite3_errmsg(db->handle));
+        return -1;
+    }
+    free(db);
+
+    return 0;
+}
+
+static void
+_calc_base(struct lms_file_info *finfo)
+{
+    int i;
+
+    for (i = finfo->path_len - 1; i >= 0; i--)
+        if (finfo->path[i] == '/') {
+            finfo->base = i;
+            return;
+        }
+}
+
+static inline void
+_update_finfo_from_stmt(struct lms_file_info *finfo, sqlite3_stmt *stmt)
+{
+    finfo->id = sqlite3_column_int64(stmt, 0);
+    finfo->path = sqlite3_column_blob(stmt, 1);
+    finfo->path_len = sqlite3_column_bytes(stmt, 1);
+    finfo->base = 0;
+    finfo->mtime = sqlite3_column_int(stmt, 2);
+    finfo->dtime = sqlite3_column_int(stmt, 3);
+    finfo->size = sqlite3_column_int(stmt, 4);
+}
+
+static inline void
+_update_finfo_from_stat(struct lms_file_info *finfo, const struct stat *st)
+{
+    finfo->mtime = st->st_mtime;
+    finfo->size = st->st_size;
+    finfo->dtime = 0;
+}
+
+static int
+_check_row(struct master_db *db, struct pinfo *pinfo)
+{
+    struct lms_file_info finfo;
+    struct stat st;
+    unsigned int flags;
+    int r, reply;
+
+    _update_finfo_from_stmt(&finfo, db->get_files);
+
+    flags = 0;
+    if (stat(finfo.path, &st) == 0) {
+        if (st.st_mtime == finfo.mtime && st.st_size == finfo.size) {
+            if (finfo.dtime == 0)
+                return 0;
+            else
+                finfo.dtime = 0;
+        } else {
+            _update_finfo_from_stat(&finfo, &st);
+            flags |= COMM_FINFO_FLAG_OUTDATED;
+        }
+    } else {
+        if (finfo.dtime)
+            return 0;
+        else
+            finfo.dtime = time(NULL);
+    }
+
+    _calc_base(&finfo);
+
+    if (_master_send_file(&pinfo->master, finfo, flags) != 0)
+        return -1;
+
+    r = _master_recv_reply(&pinfo->master, &pinfo->poll, &reply,
+                           pinfo->lms->slave_timeout);
+    if (r < 0)
+        return -2;
+    else if (r == 1) {
+        fprintf(stderr, "ERROR: slave took too long, restart %d\n",
+                pinfo->child);
+        if (lms_restart_slave(pinfo, _slave_work) != 0)
+            return -3;
+        return 1;
+    } else {
+        if (reply < 0) {
+            /* XXX callback library users to inform error. */
+            fprintf(stderr, "ERROR: pid=%d failed to parse \"%s\".\n",
+                    getpid(), finfo.path);
+            return (-reply) << 8;
+        } else
+            return reply;
+    }
+}
+
+static int
+_init_sync_wait(struct pinfo *pinfo, int restart)
+{
+    int r, reply;
+
+    do {
+        r = _master_recv_reply(&pinfo->master, &pinfo->poll, &reply,
+                               pinfo->lms->slave_timeout);
+        if (r < 0)
+            return -1;
+        else if (r == 1 && restart) {
+            fprintf(stderr, "ERROR: slave took too long, restart %d\n",
+                    pinfo->child);
+            if (lms_restart_slave(pinfo, _slave_work) != 0)
+                return -2;
+        }
+    } while (r != 0 && restart);
+
+    return r;
+}
+
+static int
+_master_dummy_send_finish(const struct fds *master)
+{
+    return 0;
+}
+
+static int
+_check(struct pinfo *pinfo, int len, char *path)
+{
+    char query[PATH_SIZE + 2];
+    struct master_db *db;
+    int r, ret;
+
+    db = _master_db_open(pinfo->lms->db_path);
+    if (!db)
+        return -1;
+
+    memcpy(query, path, len);
+    query[len] = '%';
+    query[len + 1] = '\0';
+    ret = lms_db_get_files(db->get_files, query, len + 1);
+    if (ret != 0)
+        goto end;
+
+    if (lms_create_slave(pinfo, _slave_work) != 0) {
+        ret = -2;
+        goto end;
+    }
+    _init_sync_wait(pinfo, 1);
+
+    do {
+        r = sqlite3_step(db->get_files);
+        if (r == SQLITE_ROW) {
+            if (_check_row(db, pinfo) < 0) {
+                fprintf(stderr, "ERROR: could not check row.\n");
+                ret = -1;
+                goto finish_slave;
+            }
+        } else if (r != SQLITE_DONE) {
+            fprintf(stderr, "ERROR: could not begin transaction: %s\n",
+                    sqlite3_errmsg(db->handle));
+            ret = -2;
+            goto finish_slave;
+        }
+    } while (r != SQLITE_DONE);
+    ret = 0;
+
+  finish_slave:
+    _master_send_finish(&pinfo->master);
+    _init_sync_wait(pinfo, 0);
+    lms_finish_slave(pinfo, _master_dummy_send_finish);
+
+  end:
+    lms_db_reset_stmt(db->get_files);
+    _master_db_close(db);
+
+    return ret;
+}
+
+int
+lms_check(lms_t *lms, const char *top_path)
+{
+    struct pinfo pinfo;
+    int r;
+    char path[PATH_SIZE];
+
+    if (!lms) {
+        r = -1;
+        goto end;
+    }
+
+    if (!top_path) {
+        r = -2;
+        goto end;
+    }
+
+    if (lms->is_processing) {
+        fprintf(stderr, "ERROR: is already processing.\n");
+        r = -3;
+        goto end;
+    }
+
+    if (!lms->parsers) {
+        fprintf(stderr, "ERROR: no plugins registered.\n");
+        r = -4;
+        goto end;
+    }
+
+    pinfo.lms = lms;
+
+    if (lms_create_pipes(&pinfo) != 0) {
+        r = -5;
+        goto end;
+    }
+
+    if (realpath(top_path, path) == NULL) {
+        perror("realpath");
+        r = -6;
+        goto close_pipes;
+    }
+
+    lms->is_processing = 1;
+    r = _check(&pinfo, strlen(path), path);
+    lms->is_processing = 0;
+
+  close_pipes:
+    lms_close_pipes(&pinfo);
+  end:
+    return r;
+}