/* plugin.c
 *
 * Copyright (C) 2002 by Jason Day
 *
 * 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
 * (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 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
 */

#include "config.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <utime.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <gdbm.h>
#include <dirent.h>
#include <errno.h>

#include <pi-dlp.h>
#include <pi-file.h>

#include "backup.h"
#include "gui.h"
#include "libplugin.h"
#include "bprefs.h"


static const char RCSID[] = "$Id: plugin.c,v 1.1.1.2 2004/07/20 16:30:35 rousseau Exp $";



/* Local static functions */
static void filename_make_legal(char *s);


/* This plugin was designed to work with version 0.99 or greater of jpilot */
void plugin_version (int *major_version, int *minor_version) {
    *major_version = 0;
    *minor_version = 99;
}

int plugin_get_name (char *name, int len) {
    strncpy (name, "Backup " VERSION, len);
    return 0;
}

int plugin_get_menu_name (char *name, int len) {
    strncpy (name, "Backup", len);
    return 0;
}

int plugin_get_help_name (char *name, int len) {
    strncpy(name, "About Backup", len);
    return 0;
}

int plugin_help (char **text, int *width, int *height) {
   *text = strdup(
	   "Backup plugin for J-Pilot\n"
           "version " VERSION "\n"
	   "by Jason Day (c) 1999-2002.\n"
	   "jasonday@worldnet.att.net\n"
	   "http://jasonday.home.att.net/\n"
	   );

   /* Specifying 0 for width and height lets GTK decide */
   *height = 0;
   *width = 0;

   return 0;
}

int plugin_startup (jp_startup_info *info) {
    jp_init();
    jp_logf (JP_LOG_DEBUG, "Backup: plugin_startup\n");

    /* Check to see if ~/.jpilot/Backup is there, or create it */
    jp_logf (JP_LOG_DEBUG, "calling check_backup_dir\n");
    if (check_backup_dir()) {
        return 1;
    }

    jp_logf (JP_LOG_DEBUG, "Backup: Loading prefs\n");
    backup_prefs_init();
    if (backup_load_prefs() < 0) {
        jp_logf (JP_LOG_WARN, "Backup: Unable to load preferences file " PREFS_FILE "\n");
    }
    else {
        jp_logf (JP_LOG_DEBUG, "Backup: loaded preferences from " PREFS_FILE "\n");
    }

    return 0;
}

int plugin_sync (int sd) {
    struct pi_file *pi_fp;
    char full_name[256];
    char db_copy_name[MAX_DBNAME + 5];
    int start;
    struct DBInfo info;
    time_t ltime;
    struct tm *now;
    char arch[28];
    char main_arch[256];
    char current_arch[256];
    char last_arch[256];
    char temp_str[256];
    GDBM_FILE active_dbf;
    GDBM_FILE inactive_dbf;
    datum key;
    datum content;
    time_t mtime;
    int ret;
    FILE *manifest;
    long backup_new;
    long persistent_archive;


    /* see if it's time to make a backup */
    if (skip_backup()) {
        jp_logf (JP_LOG_GUI, "Backup: Skipping backup\n");
        return 0;
    }

    /* create the archive directory */
    time (&ltime);
    now = localtime (&ltime);
    sprintf (arch, "Archive_%4d-%02d-%02d@%02d:%02d:%02d",
             now->tm_year + 1900,
             now->tm_mon + 1,
             now->tm_mday,
             now->tm_hour,
             now->tm_min,
             now->tm_sec);
    get_backup_file_name (arch, current_arch, 255);
    if (mkdir (current_arch, 0755)) {
        /* Can't create directory */
        jp_logf (JP_LOG_FATAL, "Can't create directory %s\n", current_arch);
        return 1;
    }
    get_backup_file_name ("LatestArchive", last_arch, 255);
    get_backup_file_name (PERSISTENT_ARCH_DIR_NAME, main_arch, 255);

    /* open the active dbm file */
    get_backup_file_name (ACTIVE_DBM, full_name, 255);
    active_dbf = gdbm_open (full_name, 512, GDBM_WRCREAT | LOCK_FLAG, 0644, 0);
    if (!active_dbf) {
        /* Can't open or create dbm file */
        jp_logf (JP_LOG_FATAL,
                 "Can't open dbm file %s\nReason: %s\n",
                 full_name,
                 gdbm_strerror (gdbm_errno));
        return 1;
    }
    /* open the inactive dbm file */
    get_backup_file_name (INACTIVE_DBM, full_name, 255);
    inactive_dbf = gdbm_open (full_name, 512, GDBM_WRCREAT | LOCK_FLAG, 0644, 0);
    if (!inactive_dbf) {
        /* Can't open or create dbm file */
        jp_logf (JP_LOG_FATAL,
                 "Can't open dbm file %s\nReason: %s\n",
                 full_name,
                 gdbm_strerror (gdbm_errno));
        return 1;
    }

    /* open the manifest file */
    get_archive_file_name (current_arch, MANIFEST, full_name, 255);
    manifest = fopen (full_name, "w");
    if (!manifest) {
        jp_logf (JP_LOG_WARN,
                 "Cannot create manifest file %s.\n"
                 "Archive directory %s cannot be automatically expired.\n",
                 full_name, current_arch);
    }

    backup_get_pref (BPREF_BACKUP_NEW, &backup_new, NULL);
    backup_get_pref (BPREF_PERSISTENT_ARCHIVE, &persistent_archive, NULL);

    start = 0;
    while (dlp_ReadDBList (sd, 0, dlpOpenRead, start, &info) > 0) {
        start = info.index + 1;

        key.dptr = info.name;
        key.dsize = strlen (info.name) + 1;

        /* see if it's in the inactive list */
        if (gdbm_exists (inactive_dbf, key)) {
            continue;
        }

        /*
         * fetch the modification time from the active list
         */
        content = gdbm_fetch (active_dbf, key);
        if (content.dptr) {
            mtime = (time_t)atoi (content.dptr);
        }
        else {
            /*
             * not contained in either of the databases; store it in
             * the correct one based on user pref
             */
            mtime = 0;

            sprintf (temp_str, "%ld", info.modifyDate);
            content.dptr = temp_str;
            content.dsize = strlen (temp_str) + 1;
            if (backup_new) {
                ret = gdbm_store (active_dbf, key, content, GDBM_INSERT);
                jp_logf (JP_LOG_DEBUG, "Storing %s in active database file\n", key.dptr);
            }
            else {
                ret = gdbm_store (inactive_dbf, key, content, GDBM_INSERT);
                jp_logf (JP_LOG_DEBUG, "Storing %s in inactive database file\n", key.dptr);
                continue;
            }
        }

        strncpy(db_copy_name, info.name, MAX_DBNAME);
	filename_make_legal (db_copy_name);
        db_copy_name[MAX_DBNAME] = '\0';
        if (info.flags & dlpDBFlagResource) {
            strcat (db_copy_name, ".prc");
        }
        else if (strncmp(db_copy_name + strlen(db_copy_name) - 4, ".pqa", 4)) {
            strcat (db_copy_name, ".pdb");
        }

        get_archive_file_name (current_arch, db_copy_name, full_name, 255);

        /* If modification times are the same then we don't need to fetch it */
        if (info.modifyDate == mtime) {
            jp_logf (JP_LOG_GUI, "Backup: %s is up to date, fetch skipped.\n", db_copy_name);
            get_archive_file_name (last_arch, db_copy_name, temp_str, 255);
            if (link (temp_str, full_name)) {
                jp_logf (JP_LOG_WARN, "Backup: Unable to link file %s, will fetch.\n", temp_str);
            }
            else {
                /* update the file manifest */
                if (manifest) {
                    fprintf (manifest, "%s\n", db_copy_name);
                }

                /*
                 * Theoretically, we shouldn't need to store the db in the
                 * persistent archive, since it hasn't changed. But, the user
                 * might have just enabled the persistent archive, in which
                 * case it won't have anything in it yet. So we store it
                 * again, just to be safe.
                 */
                if (persistent_archive) {
                    store_persistent_archive (main_arch, full_name, FALSE);
                }

                continue;
            }
        }

        jp_logf (JP_LOG_GUI, "Backup: Fetching '%s'... ", info.name);

        /* update the active dbm file */
        sprintf (temp_str, "%ld", info.modifyDate);
        content.dptr = temp_str;
        content.dsize = strlen (temp_str) + 1;
        ret = gdbm_store (active_dbf, key, content, GDBM_REPLACE);

        info.flags &= 0xff;

        pi_fp = pi_file_create (full_name, &info);
        if (pi_fp==0) {
            jp_logf (JP_LOG_WARN, "Failed, unable to create file %s\n", full_name);
            continue;
        }
        if (pi_file_retrieve (pi_fp, sd, 0) < 0) {
            jp_logf (JP_LOG_WARN, "Failed, unable to back up database\n");
        }
        else {
            jp_logf (JP_LOG_GUI, "OK\n");

            /* update the file manifest */
            if (manifest) {
                fprintf (manifest, "%s\n", db_copy_name);
            }
        }
        pi_file_close (pi_fp);

	/* Now, if we are using the persistent archive, create a hard link to the
	 * persistent archive directory. */
        if (persistent_archive) {
            store_persistent_archive (main_arch, full_name, TRUE);
        }
    }

    /* close the files */
    gdbm_close (active_dbf);
    gdbm_close (inactive_dbf);
    if (manifest) {
        fclose (manifest);
    }

    /* update the latest archive link */
    unlink (last_arch);
    symlink (arch, last_arch);

    expire_archives();
    jp_logf (JP_LOG_GUI, "Backup: backup complete\n");

    return 0;
}

int plugin_post_sync() {
    jp_logf (JP_LOG_DEBUG, "Backup: plugin_post_sync\n");
    /* For some reason, calling this will cause the sync process to die
     * horribly.  On the other hand, it doesn't do anything useful anyway,
     * since J-Pilot does not display a plugin's gui after a sync. So I'm
     * just going to comment it out for now.
     */
    /*display_databases();*/
    return 0;
}


/*
 * This function is called by J-Pilot when the user selects this plugin
 * from the plugin menu, or from the search window when a search result
 * record is chosen.  In the latter case, unique ID will be set.  This
 * application should go directly to that record in the case.
 */
int plugin_gui (GtkWidget *vbox, GtkWidget *hbox, unsigned int unique_id) {
    jp_logf(JP_LOG_DEBUG, "Backup: plugin gui started, unique_id=%d\n", unique_id);
    return init_gui (vbox, hbox, unique_id);
}

int plugin_gui_cleanup() {
    jp_logf (JP_LOG_DEBUG, "plugin_gui_cleanup()\n");
    return destroy_gui();
}


/*
 * Called when JPilot shuts down.
 */
int plugin_exit_cleanup() {
    jp_logf (JP_LOG_DEBUG, "plugin_exit_cleanup()\n");

    backup_free_prefs();
    return 0;
}

static void filename_make_legal (char *s) {
    char *p;

    for (p = s; *p; p++) {
        if (*p == '/') {
            *p = '?';
        }
    }
}

