2 * Copyright (C) 2008-2011 by ProFUSION embedded systems
3 * Copyright (C) 2007 by INdT
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public License
7 * as published by the Free Software Foundation; either version 2.1 of
8 * the License, or (at your option) any later version.
10 * This library 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 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * @author Gustavo Sverzut Barbieri <barbieri@profusion.mobi>
34 #include "lightmediascanner.h"
35 #include "lightmediascanner_private.h"
36 #include "lightmediascanner_db_private.h"
40 sqlite3_stmt *transaction_begin;
41 sqlite3_stmt *transaction_commit;
42 sqlite3_stmt *get_file_info;
43 sqlite3_stmt *insert_file_info;
44 sqlite3_stmt *update_file_info;
45 sqlite3_stmt *delete_file_info;
46 sqlite3_stmt *set_file_dtime;
49 /***********************************************************************
50 * Master-Slave communication.
51 ***********************************************************************/
54 _master_send_path(const struct fds *master, int plen, int dlen, const char *p)
61 if (write(master->w, lengths, sizeof(lengths)) < 0) {
66 if (write(master->w, p, plen) < 0) {
75 _master_send_finish(const struct fds *master)
77 const int lengths[2] = {-1, -1};
79 if (write(master->w, lengths, sizeof(lengths)) < 0) {
87 _master_recv_reply(const struct fds *master, struct pollfd *pfd, int *reply, int timeout)
91 r = poll(pfd, 1, timeout);
100 if (read(master->r, reply, sizeof(*reply)) != sizeof(*reply)) {
109 _slave_send_reply(const struct fds *slave, int reply)
111 if (write(slave->w, &reply, sizeof(reply)) == 0) {
119 _slave_recv_path(const struct fds *slave, int *plen, int *dlen, char *path)
123 r = read(slave->r, lengths, sizeof(lengths));
124 if (r != sizeof(lengths)) {
134 if (*plen > PATH_SIZE) {
135 fprintf(stderr, "ERROR: path too long (%d/%d)\n", *plen, PATH_SIZE);
139 r = read(slave->r, path, *plen);
141 fprintf(stderr, "ERROR: could not read whole path %d/%d\n", r, *plen);
150 /***********************************************************************
152 ***********************************************************************/
155 _db_compile_all_stmts(struct db *db)
160 db->transaction_begin = lms_db_compile_stmt_begin_transaction(handle);
161 if (!db->transaction_begin)
164 db->transaction_commit = lms_db_compile_stmt_end_transaction(handle);
165 if (!db->transaction_commit)
168 db->get_file_info = lms_db_compile_stmt_get_file_info(handle);
169 if (!db->get_file_info)
172 db->insert_file_info = lms_db_compile_stmt_insert_file_info(handle);
173 if (!db->insert_file_info)
176 db->update_file_info = lms_db_compile_stmt_update_file_info(handle);
177 if (!db->update_file_info)
180 db->delete_file_info = lms_db_compile_stmt_delete_file_info(handle);
181 if (!db->delete_file_info)
184 db->set_file_dtime = lms_db_compile_stmt_set_file_dtime(handle);
185 if (!db->set_file_dtime)
192 _db_open(const char *db_path)
196 db = calloc(1, sizeof(*db));
202 if (sqlite3_open(db_path, &db->handle) != SQLITE_OK) {
203 fprintf(stderr, "ERROR: could not open DB \"%s\": %s\n",
204 db_path, sqlite3_errmsg(db->handle));
208 if (lms_db_create_core_tables_if_required(db->handle) != 0) {
209 fprintf(stderr, "ERROR: could not setup tables and indexes.\n");
216 sqlite3_close(db->handle);
222 _db_close(struct db *db)
224 if (db->transaction_begin)
225 lms_db_finalize_stmt(db->transaction_begin, "transaction_begin");
227 if (db->transaction_commit)
228 lms_db_finalize_stmt(db->transaction_commit, "transaction_commit");
230 if (db->get_file_info)
231 lms_db_finalize_stmt(db->get_file_info, "get_file_info");
233 if (db->insert_file_info)
234 lms_db_finalize_stmt(db->insert_file_info, "insert_file_info");
236 if (db->update_file_info)
237 lms_db_finalize_stmt(db->update_file_info, "update_file_info");
239 if (db->delete_file_info)
240 lms_db_finalize_stmt(db->delete_file_info, "delete_file_info");
242 if (db->set_file_dtime)
243 lms_db_finalize_stmt(db->set_file_dtime, "set_file_dtime");
245 if (sqlite3_close(db->handle) != SQLITE_OK) {
246 fprintf(stderr, "ERROR: clould not close DB: %s\n",
247 sqlite3_errmsg(db->handle));
257 * 0: file found and nothing changed
258 * 1: file not found or mtime/size is different
262 _retrieve_file_status(struct db *db, struct lms_file_info *finfo)
267 if (stat(finfo->path, &st) != 0) {
272 r = lms_db_get_file_info(db->get_file_info, finfo);
274 if (st.st_mtime <= finfo->mtime && finfo->size == (size_t)st.st_size)
277 finfo->mtime = st.st_mtime;
278 finfo->size = st.st_size;
282 finfo->mtime = st.st_mtime;
283 finfo->size = st.st_size;
290 _ctxt_init(struct lms_context *ctxt, const lms_t *lms, sqlite3 *db)
292 ctxt->cs_conv = lms->cs_conv;
297 lms_parsers_setup(lms_t *lms, sqlite3 *db)
299 struct lms_context ctxt;
302 _ctxt_init(&ctxt, lms, db);
304 for (i = 0; i < lms->n_parsers; i++) {
305 lms_plugin_t *plugin;
308 plugin = lms->parsers[i].plugin;
309 r = plugin->setup(plugin, &ctxt);
311 fprintf(stderr, "ERROR: parser \"%s\" failed to setup: %d.\n",
313 plugin->finish(plugin, &ctxt);
314 lms_parser_del_int(lms, i);
315 i--; /* cancel i++ */
323 lms_parsers_start(lms_t *lms, sqlite3 *db)
325 struct lms_context ctxt;
328 _ctxt_init(&ctxt, lms, db);
330 for (i = 0; i < lms->n_parsers; i++) {
331 lms_plugin_t *plugin;
334 plugin = lms->parsers[i].plugin;
335 r = plugin->start(plugin, &ctxt);
337 fprintf(stderr, "ERROR: parser \"%s\" failed to start: %d.\n",
339 plugin->finish(plugin, &ctxt);
340 lms_parser_del_int(lms, i);
341 i--; /* cancel i++ */
349 lms_parsers_finish(lms_t *lms, sqlite3 *db)
351 struct lms_context ctxt;
354 _ctxt_init(&ctxt, lms, db);
356 for (i = 0; i < lms->n_parsers; i++) {
357 lms_plugin_t *plugin;
360 plugin = lms->parsers[i].plugin;
361 r = plugin->finish(plugin, &ctxt);
363 fprintf(stderr, "ERROR: parser \"%s\" failed to finish: %d.\n",
371 lms_parsers_check_using(lms_t *lms, void **parser_match, struct lms_file_info *finfo)
376 for (i = 0; i < lms->n_parsers; i++) {
377 lms_plugin_t *plugin;
380 plugin = lms->parsers[i].plugin;
381 r = plugin->match(plugin, finfo->path, finfo->path_len, finfo->base);
391 lms_parsers_run(lms_t *lms, sqlite3 *db, void **parser_match, struct lms_file_info *finfo)
393 struct lms_context ctxt;
394 int i, failed, available;
396 _ctxt_init(&ctxt, lms, db);
400 for (i = 0; i < lms->n_parsers; i++) {
401 lms_plugin_t *plugin;
403 plugin = lms->parsers[i].plugin;
404 if (parser_match[i]) {
408 r = plugin->parse(plugin, &ctxt, finfo, parser_match[i]);
416 else if (failed == available)
419 return 1; /* non critical */
423 _db_and_parsers_setup(lms_t *lms, struct db **db_ret, void ***parser_match_ret)
429 db = _db_open(lms->db_path);
435 if (lms_parsers_setup(lms, db->handle) != 0) {
436 fprintf(stderr, "ERROR: could not setup parsers.\n");
441 if (_db_compile_all_stmts(db) != 0) {
442 fprintf(stderr, "ERROR: could not compile statements.\n");
447 if (lms_parsers_start(lms, db->handle) != 0) {
448 fprintf(stderr, "ERROR: could not start parsers.\n");
452 if (lms->n_parsers < 1) {
453 fprintf(stderr, "ERROR: no parser could be started, exit.\n");
458 parser_match = malloc(lms->n_parsers * sizeof(*parser_match));
465 *parser_match_ret = parser_match;
470 lms_parsers_finish(lms, db->handle);
477 * LMS_PROGRESS_STATUS_UP_TO_DATE
478 * LMS_PROGRESS_STATUS_PROCESSED
479 * LMS_PROGRESS_STATUS_SKIPPED
483 _db_and_parsers_process_file(lms_t *lms, struct db *db, void **parser_match,
484 char *path, int path_len, int path_base,
485 unsigned int update_id)
487 struct lms_file_info finfo;
491 finfo.path_len = path_len;
492 finfo.base = path_base;
494 r = _retrieve_file_status(db, &finfo);
497 return LMS_PROGRESS_STATUS_UP_TO_DATE;
500 finfo.itime = time(NULL);
501 lms_db_set_file_dtime(db->set_file_dtime, &finfo);
502 return LMS_PROGRESS_STATUS_PROCESSED;
504 fprintf(stderr, "ERROR: could not detect file status.\n");
508 used = lms_parsers_check_using(lms, parser_match, &finfo);
510 return LMS_PROGRESS_STATUS_SKIPPED;
513 finfo.itime = time(NULL);
515 r = lms_db_update_file_info(db->update_file_info, &finfo, update_id);
517 r = lms_db_insert_file_info(db->insert_file_info, &finfo, update_id);
520 fprintf(stderr, "ERROR: could not register path in DB\n");
524 r = lms_parsers_run(lms, db->handle, parser_match, &finfo);
526 fprintf(stderr, "ERROR: pid=%d failed to parse \"%s\".\n",
527 getpid(), finfo.path);
528 lms_db_delete_file_info(db->delete_file_info, &finfo);
532 return LMS_PROGRESS_STATUS_PROCESSED;
536 _slave_work(struct pinfo *pinfo)
538 lms_t *lms = pinfo->common.lms;
539 struct fds *fds = &pinfo->slave;
541 char path[PATH_SIZE];
544 unsigned int total_committed, counter;
546 r = _db_and_parsers_setup(lms, &db, &parser_match);
550 r = lms_db_update_id_get(db->handle);
552 fprintf(stderr, "ERROR: could not get global update id.\n");
556 pinfo->common.update_id = r + 1;
560 lms_db_begin_transaction(db->transaction_begin);
562 while (((r = _slave_recv_path(fds, &len, &base, path)) == 0) && len > 0) {
563 r = _db_and_parsers_process_file(
564 lms, db, parser_match, path, len, base, pinfo->common.update_id);
566 _slave_send_reply(fds, r);
569 (r == LMS_PROGRESS_STATUS_UP_TO_DATE ||
570 r == LMS_PROGRESS_STATUS_SKIPPED))
574 if (counter > lms->commit_interval) {
575 if (!total_committed) {
576 total_committed += counter;
577 lms_db_update_id_set(db->handle, pinfo->common.update_id);
580 lms_db_end_transaction(db->transaction_commit);
581 lms_db_begin_transaction(db->transaction_begin);
587 total_committed += counter;
588 lms_db_update_id_set(db->handle, pinfo->common.update_id);
591 lms_db_end_transaction(db->transaction_commit);
595 lms_parsers_finish(lms, db->handle);
602 /***********************************************************************
604 ***********************************************************************/
607 _consume_garbage(struct pollfd *pfd)
611 while ((r = poll(pfd, 1, 0)) > 0) {
612 if (pfd->revents & (POLLERR | POLLHUP | POLLNVAL))
614 else if (pfd->revents & POLLIN) {
617 read(pfd->fd, &c, sizeof(c));
625 _close_fds(struct fds *fds)
630 if (close(fds->r) != 0) {
635 if (close(fds->w) != 0) {
644 lms_close_pipes(struct pinfo *pinfo)
648 r = _close_fds(&pinfo->master);
649 r += _close_fds(&pinfo->slave);
655 lms_create_pipes(struct pinfo *pinfo)
659 if (pipe(fds) != 0) {
663 pinfo->master.r = fds[0];
664 pinfo->slave.w = fds[1];
666 if (pipe(fds) != 0) {
668 close(pinfo->master.r);
669 close(pinfo->slave.w);
672 pinfo->slave.r = fds[0];
673 pinfo->master.w = fds[1];
675 pinfo->poll.fd = pinfo->master.r;
676 pinfo->poll.events = POLLIN;
682 lms_create_slave(struct pinfo *pinfo, int (*work)(struct pinfo *pinfo))
686 pinfo->child = fork();
687 if (pinfo->child == -1) {
692 if (pinfo->child > 0)
695 _close_fds(&pinfo->master);
698 lms_free(pinfo->common.lms);
700 return r; /* shouldn't reach anyway... */
709 r = waitpid(pid, &status, 0);
719 lms_finish_slave(struct pinfo *pinfo, int (*finish)(const struct fds *fds))
723 if (pinfo->child <= 0)
726 r = finish(&pinfo->master);
728 r = _waitpid(pinfo->child);
730 r = kill(pinfo->child, SIGKILL);
734 r =_waitpid(pinfo->child);
742 lms_restart_slave(struct pinfo *pinfo, int (*work)(struct pinfo *pinfo))
746 if (waitpid(pinfo->child, &status, WNOHANG) > 0) {
747 if (WIFEXITED(status)) {
750 code = WEXITSTATUS(status);
752 fprintf(stderr, "ERROR: slave returned %d, exit.\n", code);
757 if (WIFSIGNALED(status)) {
760 code = WTERMSIG(status);
761 fprintf(stderr, "ERROR: slave was terminated by signal %d.\n",
769 if (kill(pinfo->child, SIGKILL))
772 if (waitpid(pinfo->child, &status, 0) < 0)
775 _consume_garbage(&pinfo->poll);
776 return lms_create_slave(pinfo, work);
780 _strcat(int base, char *path, const char *name)
782 int new_len, name_len;
784 name_len = strlen(name);
785 new_len = base + name_len;
787 if (new_len >= PATH_SIZE) {
790 "ERROR: path concatenation too long %d of %d "
791 "available: \"%s\" + \"%s\"\n", new_len, PATH_SIZE,
796 memcpy(path + base, name, name_len + 1);
802 _report_progress(struct cinfo *info, const char *path, int path_len, lms_progress_status_t status)
804 lms_progress_callback_t cb;
805 lms_t *lms = info->lms;
807 cb = lms->progress.cb;
811 cb(lms, path, path_len, status, lms->progress.data);
815 _process_file(struct cinfo *info, int base, char *path, const char *name)
817 struct pinfo *pinfo = (struct pinfo *)info;
818 int new_len, reply, r;
820 new_len = _strcat(base, path, name);
824 if (_master_send_path(&pinfo->master, new_len, base, path) != 0)
827 r = _master_recv_reply(&pinfo->master, &pinfo->poll, &reply,
828 pinfo->common.lms->slave_timeout);
830 _report_progress(info, path, new_len, LMS_PROGRESS_STATUS_ERROR_COMM);
833 fprintf(stderr, "ERROR: slave took too long, restart %d\n",
835 _report_progress(info, path, new_len, LMS_PROGRESS_STATUS_KILLED);
836 if (lms_restart_slave(pinfo, _slave_work) != 0)
841 fprintf(stderr, "ERROR: pid=%d failed to parse \"%s\".\n",
844 info, path, new_len, LMS_PROGRESS_STATUS_ERROR_PARSE);
847 _report_progress(info, path, new_len, r);
853 _process_file_single_process(struct cinfo *info, int base, char *path, const char *name)
855 struct sinfo *sinfo = (struct sinfo *)info;
858 void **parser_match = sinfo->parser_match;
859 struct db *db = sinfo->db;
860 lms_t *lms = sinfo->common.lms;
862 new_len = _strcat(base, path, name);
866 r = _db_and_parsers_process_file(lms, db, parser_match, path, new_len,
867 base, sinfo->common.update_id);
869 fprintf(stderr, "ERROR: pid=%d failed to parse \"%s\".\n",
871 _report_progress(info, path, new_len, LMS_PROGRESS_STATUS_ERROR_PARSE);
875 if (r != LMS_PROGRESS_STATUS_UP_TO_DATE)
876 sinfo->commit_counter++;
878 if (sinfo->commit_counter > lms->commit_interval) {
879 if (!sinfo->total_committed) {
880 sinfo->total_committed += sinfo->commit_counter;
881 lms_db_update_id_set(db->handle, sinfo->common.update_id);
884 lms_db_end_transaction(db->transaction_commit);
885 lms_db_begin_transaction(db->transaction_begin);
886 sinfo->commit_counter = 0;
889 _report_progress(info, path, new_len, r);
894 static int _process_dir(struct cinfo *info, int base, char *path, const char *name, process_file_callback_t process_file);
897 _process_unknown(struct cinfo *info, int base, char *path, const char *name, process_file_callback_t process_file)
902 new_len = _strcat(base, path, name);
906 if (stat(path, &st) != 0) {
911 if (S_ISREG(st.st_mode)) {
912 int r = process_file(info, base, path, name);
913 if (r >= 0) /* if success and ignore non-fatal errors */
916 } else if (S_ISDIR(st.st_mode)) {
917 int r = _process_dir(info, base, path, name, process_file);
918 if (r >= 0) /* ignore non-fatal errors */
923 "INFO: %s is neither a directory nor a regular file.\n", path);
929 _process_dir(struct cinfo *info, int base, char *path, const char *name, process_file_callback_t process_file)
931 lms_t *lms = info->lms;
936 new_len = _strcat(base, path, name);
939 else if (new_len + 1 >= PATH_SIZE) {
940 fprintf(stderr, "ERROR: path too long\n");
954 while ((de = readdir(dir)) != NULL && !lms->stop_processing) {
955 if (de->d_name[0] == '.')
957 if (de->d_type == DT_REG) {
958 if (process_file(info, new_len, path, de->d_name) < 0) {
960 "ERROR: unrecoverable error parsing file, "
961 "exit \"%s\".\n", path);
962 path[new_len - 1] = '\0';
966 } else if (de->d_type == DT_DIR) {
968 info, new_len, path, de->d_name, process_file) < 0) {
970 "ERROR: unrecoverable error parsing dir, "
971 "exit \"%s\".\n", path);
972 path[new_len - 1] = '\0';
976 } else if (de->d_type == DT_UNKNOWN) {
977 if (_process_unknown(
978 info, new_len, path, de->d_name, process_file) < 0) {
980 "ERROR: unrecoverable error parsing DT_UNKNOWN, "
981 "exit \"%s\".\n", path);
982 path[new_len - 1] = '\0';
995 _lms_process_check_valid(lms_t *lms, const char *path)
1003 if (lms->is_processing) {
1004 fprintf(stderr, "ERROR: is already processing.\n");
1008 if (!lms->parsers) {
1009 fprintf(stderr, "ERROR: no plugins registered.\n");
1017 _process_trigger(struct cinfo *info, const char *top_path, process_file_callback_t process_file)
1019 char path[PATH_SIZE], *bname;
1020 lms_t *lms = info->lms;
1024 if (realpath(top_path, path) == NULL) {
1029 /* search '/' backwards, split dirname and basename, note realpath usage */
1031 for (; len >= 0 && path[len] != '/'; len--);
1033 bname = strdup(path + len);
1034 if (bname == NULL) {
1039 lms->is_processing = 1;
1040 lms->stop_processing = 0;
1041 r = _process_unknown(info, len, path, bname, process_file);
1042 lms->is_processing = 0;
1043 lms->stop_processing = 0;
1050 * Process the given directory or file.
1052 * This will add or update media found in the given directory or its children.
1054 * @param lms previously allocated Light Media Scanner instance.
1055 * @param top_path top directory or file to scan.
1057 * @return On success 0 is returned.
1060 lms_process(lms_t *lms, const char *top_path)
1065 r = _lms_process_check_valid(lms, top_path);
1069 pinfo.common.lms = lms;
1071 if (lms_create_pipes(&pinfo) != 0) {
1076 if (lms_create_slave(&pinfo, _slave_work) != 0) {
1081 r = _process_trigger(&pinfo.common, top_path, _process_file);
1083 lms_finish_slave(&pinfo, _master_send_finish);
1085 lms_close_pipes(&pinfo);
1091 * Process the given directory or file *without fork()-ing* into child process.
1093 * This will add or update media found in the given directory or its children.
1094 * Note that if a parser hangs during the process, this call will also hang.
1096 * @param lms previously allocated Light Media Scanner instance.
1097 * @param top_path top directory or file to scan.
1099 * @return On success 0 is returned.
1102 lms_process_single_process(lms_t *lms, const char *top_path)
1107 r = _lms_process_check_valid(lms, top_path);
1111 sinfo.common.lms = lms;
1112 sinfo.commit_counter = 0;
1113 sinfo.total_committed = 0;
1115 r = _db_and_parsers_setup(sinfo.common.lms, &sinfo.db, &sinfo.parser_match);
1119 r = lms_db_update_id_get(sinfo.db->handle);
1121 fprintf(stderr, "ERROR: could not get global update id.\n");
1125 sinfo.common.update_id = r + 1;
1127 lms_db_begin_transaction(sinfo.db->transaction_begin);
1129 r = _process_trigger(&sinfo.common, top_path, _process_file_single_process);
1131 /* Check only if there are remaining commits to do */
1132 if (sinfo.commit_counter) {
1133 sinfo.total_committed += sinfo.commit_counter;
1134 lms_db_update_id_set(sinfo.db->handle, sinfo.common.update_id);
1137 lms_db_end_transaction(sinfo.db->transaction_commit);
1140 free(sinfo.parser_match);
1141 lms_parsers_finish(lms, sinfo.db->handle);
1142 _db_close(sinfo.db);
1147 lms_stop_processing(lms_t *lms)
1151 if (!lms->is_processing)
1154 lms->stop_processing = 1;