Logo Search packages:      
Sourcecode: jpilot-backup version File versions  Download package

plugin.c

/* plugin.c
 *
 * Copyright (C) 2002-2006 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 <glib.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.6 2009-02-22 08:32:16 rousseau Exp $";

/* Local static functions */
static void filename_make_legal(char *s);
static void purge_db(GDBM_FILE dbf, GHashTable *hashtable);
static gboolean rm_func(gpointer key, gpointer value, gpointer user_data);


/* 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-2008.\n"
         "jason@jlogday.com\n"
         "http://www.jlogday.com/\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;
#ifdef PILOT_LINK_0_12
    pi_buffer_t *buffer;
#endif
    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;
    GHashTable *hashtable;


    /* 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);

    hashtable = g_hash_table_new(g_str_hash, g_int_equal);

    start = 0;
#ifdef PILOT_LINK_0_12
    buffer = pi_buffer_new(32 * sizeof(struct DBInfo));

    while (dlp_ReadDBList(sd, 0, dlpDBListRAM | dlpDBListMultiple, start, buffer) > 0) {
        int dbIndex;

        for (dbIndex = 0; dbIndex < (buffer->used / sizeof(struct DBInfo)); dbIndex++) {
            memcpy(&info, buffer->data + (dbIndex * sizeof(struct DBInfo)), sizeof(struct DBInfo));
#else
    while (dlp_ReadDBList (sd, 0, dlpOpenRead, start, &info) > 0) {
#endif
        start = info.index + 1;

        key.dptr = info.name;
        key.dsize = strlen (info.name) + 1;
        g_hash_table_insert(hashtable, g_strdup(info.name), GINT_TO_POINTER(1));

        /* see if it's in the inactive list */
        /*if (gdbm_exists (inactive_dbf, key)) {*/
        content = gdbm_fetch (inactive_dbf, key);
        if (content.dptr) {
            mtime = (time_t)atoi (content.dptr);
            free(content.dptr);

            /*
             * If the mtime of this database file is currently 0 in the dbf,
             * that means it was removed at some time from the handheld but is
             * now present again.  So update the dbm record with the current
             * mtime.
             */
            if (mtime == 0) {
                sprintf (temp_str, "%ld", info.modifyDate);
                content.dptr = temp_str;
                content.dsize = strlen (temp_str) + 1;
                ret = gdbm_store (inactive_dbf, key, content, GDBM_REPLACE);
                jp_logf (JP_LOG_DEBUG, "Updating mtime of %s in inactive database file\n", key.dptr);
            }

            continue;
        }

        /*
         * fetch the modification time from the active list
         */
        content = gdbm_fetch (active_dbf, key);
        if (content.dptr) {
            mtime = (time_t)atoi (content.dptr);
            free(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;
        }
#ifdef PILOT_LINK_0_12
        if (pi_file_retrieve (pi_fp, sd, 0, NULL) < 0) {
#else
        if (pi_file_retrieve (pi_fp, sd, 0) < 0) {
#endif
            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);
        }
    }
#ifdef PILOT_LINK_0_12
    }
    pi_buffer_free(buffer);
#endif

    /* set mtime to 0 for databases which are no longer on the handheld */
    purge_db(active_dbf, hashtable);
    purge_db(inactive_dbf, hashtable);

    /* free the hashtable */
    g_hash_table_freeze(hashtable);
    g_hash_table_foreach_remove(hashtable, (GHRFunc)rm_func, NULL);
    g_hash_table_destroy(hashtable);

    /* close the dbm 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;
}

static gboolean rm_func(gpointer key, gpointer value, gpointer user_data) {
    g_free(key);
    return TRUE;
}

/*
 * Iterates over each entry in the dbm file and checks whether that entry
 * exists in the hashtable.  If an entry in the dbm does not exist in the
 * hashtable, then its data (representing the mtime of the database file) is
 * set to zero.
 */
static void purge_db(GDBM_FILE dbf, GHashTable *hashtable) {
    datum key, nextkey;
    datum content;
    char *str = "0";

    key = gdbm_firstkey (dbf);
    while (key.dptr) {
        jp_logf (JP_LOG_DEBUG, "Retrieved %s from database file\n", key.dptr);

        if (!g_hash_table_lookup(hashtable, key.dptr)) {
            /* Not in hashtable, set the mtime to 0 */
            content.dptr = str;
            content.dsize = strlen (str) + 1;
            gdbm_store (dbf, key, content, GDBM_REPLACE);
        }

        nextkey = gdbm_nextkey (dbf, key);
        free (key.dptr);
        key = nextkey;
    }
}

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 = '?';
        }
    }
}


Generated by  Doxygen 1.6.0   Back to index