summaryrefslogtreecommitdiffstats
path: root/apps/plugins/tagcache/tagcache.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/tagcache/tagcache.c')
-rw-r--r--apps/plugins/tagcache/tagcache.c583
1 files changed, 511 insertions, 72 deletions
diff --git a/apps/plugins/tagcache/tagcache.c b/apps/plugins/tagcache/tagcache.c
index 55ca2e2695..cce9efbed9 100644
--- a/apps/plugins/tagcache/tagcache.c
+++ b/apps/plugins/tagcache/tagcache.c
@@ -22,16 +22,19 @@
/*Plugin Includes*/
#include "plugin.h"
+#include "errno.h"
+
/* Redefinitons of ANSI C functions. */
#include "lib/wrappers.h"
#include "lib/helper.h"
static void thread_create(void);
-static void thread(void); /* the thread running it all */
+static void thread(void); /* the thread running commit*/
static void allocate_tempbuf(void);
static void free_tempbuf(void);
static bool do_timed_yield(void);
static void _log(const char *fmt, ...);
+static bool logdump(bool append);
/*Aliases*/
#if 0
#ifdef ROCKBOX_HAS_LOGF
@@ -41,7 +44,6 @@ static void _log(const char *fmt, ...);
#endif
#endif
-
#define logf _log
#define sleep rb->sleep
#define qsort rb->qsort
@@ -49,6 +51,7 @@ static void _log(const char *fmt, ...);
#define write(x,y,z) rb->write(x,y,z)
#define ftruncate rb->ftruncate
#define remove rb->remove
+#define rename rb->rename
#define vsnprintf rb->vsnprintf
#define mkdir rb->mkdir
@@ -60,20 +63,26 @@ static void _log(const char *fmt, ...);
#define current_tick (*rb->current_tick)
#define crc_32(x,y,z) rb->crc_32(x,y,z)
+#define plugin_get_buffer rb->plugin_get_buffer
-
-#if !defined(SIMULATOR) && !defined(APPLICATION)
-
-#define errno (*rb->__errno())
-#endif
-#define ENOENT 2 /* No such file or directory */
-#define EEXIST 17 /* File exists */
#define MAX_LOG_SIZE 16384
#define EV_EXIT MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0xFF)
-#define EV_ACTION MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0x02)
#define EV_STARTUP MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0x01)
+#define BACKUP_DIRECTORY PLUGIN_APPS_DATA_DIR "/db_commit"
+
+#define RC_SUCCESS 0
+#define RC_USER_CANCEL 2
+
+enum fcpy_op_flags
+{
+ FCPY_MOVE = 0x00, /* Is a move operation (default) */
+ FCPY_COPY = 0x01, /* Is a copy operation */
+ FCPY_OVERWRITE = 0x02, /* Overwrite destination */
+ FCPY_EXDEV = 0x04, /* Actually copy/move across volumes */
+};
+
/* communication to the worker thread */
static struct
{
@@ -85,6 +94,23 @@ static struct
long last_useraction_tick;
} gThread;
+/* status of db and commit */
+static struct
+{
+ long last_check;
+ bool do_commit;
+ bool auto_commit;
+ bool commit_ready;
+ bool db_exists;
+ bool have_backup;
+ bool bu_exists;
+} gStatus;
+
+static unsigned char logbuffer[MAX_LOG_SIZE + 1];
+static int log_font_h = -1;
+static int logindex;
+static bool logwrap;
+static bool logenabled = true;
/*Support Fns*/
/* open but with a builtin printf for assembling the path */
@@ -103,8 +129,6 @@ int open_pathfmt(char *buf, size_t size, int oflag, const char *pathfmt, ...)
static void sleep_yield(void)
{
- rb->queue_remove_from_head(&gThread.queue, EV_ACTION);
- rb->queue_post(&gThread.queue, EV_ACTION, 0);
sleep(1);
#undef yield
rb->yield();
@@ -159,18 +183,10 @@ bool user_check_tag(int index_type, char* build_idx_buf)
/* paste the whole tagcache.c file */
#include "../tagcache.c"
-static bool logdump(bool append);
-static unsigned char logbuffer[MAX_LOG_SIZE + 1];
-static int log_font_h = -1;
-static int logindex;
-static bool logwrap;
-static bool logenabled = true;
-
static void check_logindex(void)
{
if(logindex >= MAX_LOG_SIZE)
{
- /* wrap */
logdump(true);
//logwrap = true;
logindex = 0;
@@ -196,7 +212,6 @@ static void _log(const char *fmt, ...)
return;
}
-
#ifdef USB_ENABLE_SERIAL
int old_logindex = logindex;
#endif
@@ -291,13 +306,11 @@ static bool logdisplay(void)
/* if negative, will be set 0 to zero later */
}
-
rb->lcd_clear_display();
if(gThread.user_index < 0 || gThread.user_index >= MAX_LOG_SIZE)
gThread.user_index = 0;
-
if(logwrap)
i = logindex;
else
@@ -373,7 +386,7 @@ static bool logdump(bool append)
/* nothing is logged just yet */
return false;
}
- if (!append)
+ if (append)
{
flags = O_CREAT|O_WRONLY|O_APPEND;
}
@@ -431,7 +444,6 @@ static bool do_timed_yield(void)
static long wakeup_tick = 0;
if (TIME_AFTER(current_tick, wakeup_tick))
{
-
yield();
wakeup_tick = current_tick + (HZ/5);
@@ -440,6 +452,402 @@ static bool do_timed_yield(void)
return false;
}
+/* copy/move a file */
+static int fcpy(const char *src, const char *target,
+ unsigned int flags, bool (*poll_cancel)(const char *))
+{
+ int rc = -1;
+
+ while (!(flags & (FCPY_COPY | FCPY_EXDEV))) {
+ if ((flags & FCPY_OVERWRITE) || !file_exists(target)) {
+ /* Rename and possibly overwrite the file */
+ if (poll_cancel && poll_cancel(src)) {
+ rc = RC_USER_CANCEL;
+ } else {
+ rc = rename(src, target);
+ }
+
+ #ifdef HAVE_MULTIVOLUME
+ if (rc < 0 && errno == EXDEV) {
+ /* Failed because cross volume rename doesn't work; force
+ a move instead */
+ flags |= FCPY_EXDEV;
+ break;
+ }
+ #endif /* HAVE_MULTIVOLUME */
+ }
+
+ return rc;
+ }
+
+ /* See if we can get the plugin buffer for the file copy buffer */
+ size_t buffersize;
+ char *buffer = (char *) plugin_get_buffer(&buffersize);
+ if (buffer == NULL || buffersize < 512) {
+ /* Not large enough, try for a disk sector worth of stack
+ instead */
+ buffersize = 512;
+ buffer = (char *)alloca(buffersize);
+ }
+
+ if (buffer == NULL) {
+ return -1;
+ }
+
+ buffersize &= ~0x1ff; /* Round buffer size to multiple of sector
+ size */
+
+ int src_fd = open(src, O_RDONLY);
+ if (src_fd >= 0) {
+ int oflag = O_WRONLY|O_CREAT;
+
+ if (!(flags & FCPY_OVERWRITE)) {
+ oflag |= O_EXCL;
+ }
+
+ int target_fd = open(target, oflag, 0666);
+ if (target_fd >= 0) {
+ off_t total_size = 0;
+ off_t next_cancel_test = 0; /* No excessive button polling */
+
+ rc = RC_SUCCESS;
+
+ while (rc == RC_SUCCESS) {
+ if (total_size >= next_cancel_test) {
+ next_cancel_test = total_size + 0x10000;
+ if (poll_cancel && poll_cancel(src)) {
+ rc = RC_USER_CANCEL;
+ break;
+ }
+ }
+
+ ssize_t bytesread = read(src_fd, buffer, buffersize);
+ if (bytesread <= 0) {
+ if (bytesread < 0) {
+ rc = -1;
+ }
+ /* else eof on buffer boundary; nothing to write */
+ break;
+ }
+
+ ssize_t byteswritten = write(target_fd, buffer, bytesread);
+ if (byteswritten < bytesread) {
+ /* Some I/O error */
+ rc = -1;
+ break;
+ }
+
+ total_size += byteswritten;
+
+ if (bytesread < (ssize_t)buffersize) {
+ /* EOF with trailing bytes */
+ break;
+ }
+ }
+
+ if (rc == RC_SUCCESS) {
+ /* If overwriting, set the correct length if original was
+ longer */
+ rc = ftruncate(target_fd, total_size);
+ }
+
+ close(target_fd);
+
+ if (rc != RC_SUCCESS) {
+ /* Copy failed. Cleanup. */
+ remove(target);
+ }
+ }
+
+ close(src_fd);
+ }
+
+ if (rc == RC_SUCCESS && !(flags & FCPY_COPY)) {
+ /* Remove the source file */
+ rc = remove(src);
+ }
+
+ return rc;
+}
+
+static bool backup_restore_tagcache(bool backup)
+{
+ struct master_header tcmh;
+ char path[MAX_PATH];
+ char bu_path[MAX_PATH];
+ int fd;
+ int rc;
+
+ if (backup)
+ {
+ if (!rb->dir_exists(BACKUP_DIRECTORY))
+ mkdir(BACKUP_DIRECTORY);
+ snprintf(path, sizeof(path), "%s/"TAGCACHE_FILE_MASTER, tc_stat.db_path);
+ snprintf(bu_path, sizeof(bu_path), "%s/"TAGCACHE_FILE_MASTER, BACKUP_DIRECTORY);
+ }
+ else
+ {
+ if (!rb->dir_exists(BACKUP_DIRECTORY))
+ return false;
+ snprintf(path, sizeof(path), "%s/"TAGCACHE_FILE_MASTER, BACKUP_DIRECTORY);
+ snprintf(bu_path, sizeof(bu_path), "%s/"TAGCACHE_FILE_MASTER, tc_stat.db_path);
+ }
+
+ fd = open(path, O_RDONLY, 0666);
+
+ if (fd >= 0)
+ {
+ rc = read(fd, &tcmh, sizeof(struct master_header));
+ close(fd);
+ if (rc != sizeof(struct master_header))
+ {
+ logf("master file read failed");
+ return false;
+ }
+ int entries = tcmh.tch.entry_count;
+
+ logf("master file %d entries", entries);
+ if (backup)
+ logf("backup db to %s", BACKUP_DIRECTORY);
+ else
+ logf("restore db to %s", tc_stat.db_path);
+
+ if (entries > 0)
+ {
+ logf("%s", path);
+ fcpy(path, bu_path, FCPY_COPY|FCPY_OVERWRITE, NULL);
+
+ for (int i = 0; i < TAG_COUNT; i++)
+ {
+ if (TAGCACHE_IS_NUMERIC(i))
+ continue;
+ snprintf(path, sizeof(path),
+ "%s/"TAGCACHE_FILE_INDEX, tc_stat.db_path, i);
+
+ snprintf(bu_path, sizeof(bu_path),
+ "%s/"TAGCACHE_FILE_INDEX, BACKUP_DIRECTORY, i);
+ /* Note: above we swapped paths in the snprintf call here we swap variables */
+ if (backup)
+ {
+ logf("%s", path);
+ if (fcpy(path, bu_path, FCPY_COPY|FCPY_OVERWRITE, NULL) < 0)
+ goto failed;
+ gStatus.have_backup = true;
+ }
+ else
+ {
+ logf("%s", bu_path);
+ if (fcpy(bu_path, path, FCPY_COPY|FCPY_OVERWRITE, NULL) < 0)
+ goto failed;
+ }
+ }
+ }
+ return true;
+ }
+failed:
+ if (backup)
+ {
+ logf("failed backup");
+ }
+
+ return false;
+}
+
+/* asks the user if they wish to quit */
+static bool confirm_quit(void)
+{
+ const struct text_message prompt =
+ { (const char*[]) {"Are you sure?", "This will abort commit."}, 2};
+ enum yesno_res response = rb->gui_syncyesno_run(&prompt, NULL, NULL);
+ return (response == YESNO_YES);
+}
+
+/* asks the user if they wish to backup/restore */
+static bool prompt_backup_restore(bool backup)
+{
+ const struct text_message bu_prompt = { (const char*[]) {"Backup database?"}, 1};
+ const struct text_message re_prompt =
+ { (const char*[]) {"Error committing,", "Restore database?"}, 2};
+ enum yesno_res response =
+ rb->gui_syncyesno_run(backup ? &bu_prompt:&re_prompt, NULL, NULL);
+ if(response == YESNO_YES)
+ return backup_restore_tagcache(backup);
+ return true;
+}
+
+static const char* list_get_name_cb(int selected_item, void* data,
+ char* buf, size_t buf_len)
+{
+ (void) data;
+ (void) buf;
+ (void) buf_len;
+
+ /* buf supplied isn't used so lets use it for a filename buffer */
+ if (TIME_AFTER(current_tick, gStatus.last_check))
+ {
+ snprintf(buf, buf_len, "%s/"TAGCACHE_FILE_NOCOMMIT, tc_stat.db_path);
+ gStatus.auto_commit = !file_exists(buf);
+ snprintf(buf, buf_len, "%s/"TAGCACHE_FILE_TEMP, tc_stat.db_path);
+ gStatus.commit_ready = file_exists(buf);
+ snprintf(buf, buf_len, "%s/"TAGCACHE_FILE_MASTER, tc_stat.db_path);
+ gStatus.db_exists = file_exists(buf);
+ snprintf(buf, buf_len, "%s/"TAGCACHE_FILE_MASTER, BACKUP_DIRECTORY);
+ gStatus.bu_exists = file_exists(buf);
+ gStatus.last_check = current_tick + HZ;
+ buf[0] = '\0';
+ }
+
+ switch(selected_item)
+ {
+
+ case 0: /* exit */
+ return ID2P(LANG_MENU_QUIT);
+ case 1: /*sep*/
+ return ID2P(VOICE_BLANK);
+ case 2: /*backup*/
+ if (!gStatus.db_exists)
+ return ID2P(VOICE_BLANK);
+ return "Backup";
+ case 3: /*restore*/
+ if (!gStatus.bu_exists)
+ return ID2P(VOICE_BLANK);
+ return "Restore";
+ case 4: /*sep*/
+ return ID2P(VOICE_BLANK);
+ case 5: /*auto commit*/
+ if (gStatus.auto_commit)
+ return "Disable auto commit";
+ else
+ return "Enable auto commit";
+ case 6: /*destroy*/
+ if (gStatus.db_exists)
+ return "Delete database";
+ /*fall through*/
+ case 7: /*sep*/
+ return ID2P(VOICE_BLANK);
+ case 8: /*commit*/
+ if (gStatus.commit_ready)
+ return "Commit";
+ else
+ return "Nothing to commit";
+ default:
+ return "?";
+ }
+}
+
+static int list_voice_cb(int list_index, void* data)
+{
+ #define MAX_MENU_NAME 32
+ if (!rb->global_settings->talk_menu)
+ return -1;
+ else
+ {
+ char buf[MAX_MENU_NAME];
+ const char* name = list_get_name_cb(list_index, data, buf, sizeof(buf));
+ long id = P2ID((const unsigned char *)name);
+ if(id>=0)
+ rb->talk_id(id, true);
+ else
+ rb->talk_spell(name, true);
+ }
+ return 0;
+}
+
+static int commit_menu(void)
+{
+ struct gui_synclist lists;
+ bool exit = false;
+ int button,i;
+ int selection, ret = 0;
+
+ rb->gui_synclist_init(&lists,list_get_name_cb,0, false, 1, NULL);
+ rb->gui_synclist_set_icon_callback(&lists, NULL);
+ rb->gui_synclist_set_nb_items(&lists, 9);
+ rb->gui_synclist_select_item(&lists, 0);
+
+ while (!exit)
+ {
+ rb->gui_synclist_draw(&lists);
+ button = rb->get_action(CONTEXT_LIST,TIMEOUT_BLOCK);
+ if (rb->gui_synclist_do_button(&lists, &button))
+ continue;
+ selection = rb->gui_synclist_get_sel_pos(&lists);
+
+ if (button == ACTION_STD_CANCEL)
+ return 0;
+ else if (button == ACTION_STD_OK)
+ {
+ switch(selection)
+ {
+ case 0: /* exit */
+ exit = true;
+ break;
+ case 1: /*sep*/
+ continue;
+ case 2: /*backup*/
+ if (!gStatus.db_exists)
+ break;
+ if (!backup_restore_tagcache(true))
+ rb->splash(HZ, "Backup failed!");
+ else
+ {
+ rb->splash(HZ, "Backup success!");
+ gStatus.bu_exists = true;
+ }
+ break;
+ case 3: /*restore*/
+ if (!gStatus.bu_exists)
+ break;
+ if (!backup_restore_tagcache(false))
+ rb->splash(HZ, "Restore failed!");
+ else
+ rb->splash(HZ, "Restore success!");
+ break;
+ case 4: /*sep*/
+ continue;
+ case 5: /*auto commit*/
+ {
+ /* build_idx_buf supplied by tagcache.c isn't being used
+ * so lets use it for a filename buffer */
+ snprintf(build_idx_buf, build_idx_bufsz,
+ "%s/" TAGCACHE_FILE_NOCOMMIT, tc_stat.db_path);
+ if(gStatus.auto_commit)
+ close(open(build_idx_buf, O_WRONLY | O_CREAT | O_TRUNC, 0666));
+ else
+ remove(build_idx_buf);
+ gStatus.auto_commit = !file_exists(build_idx_buf);
+ break;
+ }
+ case 6: /*destroy*/
+ {
+ if (!gStatus.db_exists)
+ break;
+ const struct text_message prompt =
+ { (const char*[]) {"Are you sure?", "This will destroy database."}, 2};
+ if (rb->gui_syncyesno_run(&prompt, NULL, NULL) == YESNO_YES)
+ remove_files();
+ break;
+ }
+ case 7: /*sep*/
+ continue;
+ case 8: /*commit*/
+ if (gStatus.commit_ready)
+ {
+ gStatus.do_commit = true;
+ exit = true;
+ }
+ break;
+
+ case MENU_ATTACHED_USB:
+ return PLUGIN_USB_CONNECTED;
+ default:
+ return 0;
+ }
+ }
+ } /*while*/
+ return ret;
+}
+
/*-----------------------------------------------------------------------------*/
/******* plugin_start ******* */
/*-----------------------------------------------------------------------------*/
@@ -451,6 +859,8 @@ enum plugin_status plugin_start(const void* parameter)
/* Turn off backlight timeout */
backlight_ignore_timeout();
+ memset(&gThread, 0, sizeof(gThread));
+ memset(&gStatus, 0, sizeof(gStatus));
memset(&tc_stat, 0, sizeof(struct tagcache_stat));
memset(&current_tcmh, 0, sizeof(struct master_header));
filenametag_fd = -1;
@@ -459,62 +869,32 @@ enum plugin_status plugin_start(const void* parameter)
if (!rb->dir_exists(tc_stat.db_path)) /* on fail use default DB path */
strlcpy(tc_stat.db_path, ROCKBOX_DIR, sizeof(tc_stat.db_path));
tc_stat.initialized = true;
+ tc_stat.commit_step = -1;
- memset(&gThread, 0, sizeof(gThread));
logf("started");
- logdump(false);
+ int result = commit_menu();
+
+ if (!gStatus.do_commit)
+ {
+ /* Turn on backlight timeout (revert to settings) */
+ backlight_use_settings();
+ return PLUGIN_OK;
+ }
+
+ logdump(false);
+ allocate_tempbuf();
+ if (gStatus.db_exists && !gStatus.have_backup && !prompt_backup_restore(true))
+ rb->splash(HZ, "Backup failed!");
thread_create();
gThread.user_index = 0;
logdisplay(); /* get something on the screen while user waits */
- allocate_tempbuf();
-
- commit();
-
- if (tc_stat.ready)
- rb->tagcache_commit_finalize();
-
- free_tempbuf();
-
- logdump(true);
- gThread.user_index++;
- logdisplay();
- rb->thread_wait(gThread.id);
- rb->queue_delete(&gThread.queue);
-
- /* Turn on backlight timeout (revert to settings) */
- backlight_use_settings();
- return PLUGIN_OK;
-}
-
-/****************** main thread + helper ******************/
-static void thread(void)
-{
- struct queue_event ev;
while (!gThread.exiting)
{
- rb->queue_wait_w_tmo(&gThread.queue, &ev, 1);
- switch (ev.id)
- {
- case SYS_USB_CONNECTED:
- rb->usb_acknowledge(SYS_USB_CONNECTED_ACK);
- logenabled = false;
- break;
- case SYS_USB_DISCONNECTED:
- logenabled = true;
- /*fall through*/
- case EV_STARTUP:
- logf("Thread Started");
- break;
- case EV_EXIT:
- return;
- default:
- break;
- }
logdisplay();
- int action = rb->get_action(CONTEXT_STD, HZ/10);
+ int action = rb->get_action(CONTEXT_STD, HZ/20);
switch( action )
{
@@ -536,8 +916,15 @@ static void thread(void)
break;
case SYS_POWEROFF:
case ACTION_STD_CANCEL:
- gThread.exiting = true;
- return;
+ if (tc_stat.commit_step >= 0 && !tc_stat.ready)
+ {
+ if (!confirm_quit())
+ break;
+ tc_stat.commit_delayed = true; /* Cancel the commit */
+ }
+ rb->queue_remove_from_head(&gThread.queue, EV_EXIT);
+ rb->queue_post(&gThread.queue, EV_EXIT, 0);
+ break;
#ifdef HAVE_TOUCHSCREEN
case ACTION_TOUCHSCREEN:
{
@@ -561,8 +948,60 @@ static void thread(void)
default:
break;
}
+ yield();
+ }
+
+ rb->thread_wait(gThread.id);
+ rb->queue_delete(&gThread.queue);
+ free_tempbuf();
+
+ if (tc_stat.commit_delayed || !tc_stat.ready)
+ {
+ remove_files();
+ if (gStatus.bu_exists && !prompt_backup_restore(false))
+ rb->splash(HZ, "Restore failed!");
+ }
+
+ if (tc_stat.ready)
+ rb->tagcache_commit_finalize();
+ /* Turn on backlight timeout (revert to settings) */
+ backlight_use_settings();
+ return PLUGIN_OK;
+}
+/****************** main thread + helper ******************/
+static void thread(void)
+{
+ struct queue_event ev;
+ while (!gThread.exiting)
+ {
+ rb->queue_wait_w_tmo(&gThread.queue, &ev, 1);
+ switch (ev.id)
+ {
+ case SYS_USB_CONNECTED:
+ rb->usb_acknowledge(SYS_USB_CONNECTED_ACK);
+ logenabled = false;
+ break;
+ case SYS_USB_DISCONNECTED:
+ logenabled = true;
+ /*fall through*/
+ case EV_STARTUP:
+ logf("Thread Started");
+ cpu_boost(true);
+ if (commit())
+ tc_stat.ready = true;
+ cpu_boost(false);
+ logdump(true);
+ gThread.user_index++;
+ logdisplay();
+ break;
+ case EV_EXIT:
+ gThread.exiting = true;
+ return;
+ default:
+ break;
+ }
yield();
}
}