DBBackup.java :  » Log » shadowlands-roadtrip » org » shadowlands » roadtrip » android » util » Android Open Source

Android Open Source » Log » shadowlands roadtrip 
shadowlands roadtrip » org » shadowlands » roadtrip » android » util » DBBackup.java
/*
 *  This file is part of Shadowlands RoadTrip - A vehicle logbook for Android.
 *
 *  Copyright (C) 2010 Jeremy D Monin <jdmonin@nand.net>
 *
 *  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 3 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, see <http://www.gnu.org/licenses/>.
 */

package org.shadowlands.roadtrip.android.util;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

import org.shadowlands.roadtrip.db.AppInfo;
import org.shadowlands.roadtrip.db.RDBAdapter;
import org.shadowlands.roadtrip.db.RDBKeyNotFoundException;
import org.shadowlands.roadtrip.db.android.RDBOpenHelper;

import android.content.Context;
import android.os.Environment;
import android.text.format.DateFormat;

/**
 * Utilities for DB backup/restore.
 * @author jdmonin
 */
public class DBBackup {

  /**
   * generic dir to be placed within SDcard for app backup;
   * we'll be using a pkg-named directory within this.
   *<P>
   * Value format is "/app_back/" (note trailing slash).
   */
  public static final String APPS_BACKDIR = "/app_back/";

  /**
   * db backup dir within our pkg-named directory.
   *<P>
   * Value format is "/db".
   */
  public static final String DB_SUBDIR = "/db";

  /**
   * Given our app context, determine the backup location, if sdcard and is mounted and writable.
   * Does not guarantee this directory exists on the SD Card.
   *
   * @param c app context, from {@link Context#getApplicationContext()}
   * @return path to a backup dir, such as "/sdcard/app_back/ourpackagename/db",
   *   or null if SD isn't mounted or we can't get the information.
   */
  public static String getDBBackupPath(Context appc)
  {
    if (! Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()))
      return null;  // TODO show a Toast?
    File sddir = Environment.getExternalStorageDirectory();
    if (sddir == null)
      return null;
    return sddir.getAbsolutePath() + APPS_BACKDIR + appc.getPackageName() + DB_SUBDIR;
  }

  /** prefix "db-" */;
  public static final String DBBACKUP_FILENAME_PREFIX = "db-";

  /** yyyymmdd-hhmm suitable for {@link android.text.format.DateFormat} */
  public static final String DBBACKUP_FILENAME_TIMESTAMP = "yyyyMMdd-kkmm";

  /** suffix ".bak" */
  public static final String DBBACKUP_FILENAME_SUFFIX = ".bak";

  /**
   * Generate a db backup filename, based on this date & time.
   * Consists of {@link #DBBACKUP_FILENAME_PREFIX}, {@link #DBBACKUP_FILENAME_TIMESTAMP}, {@link #DBBACKUP_FILENAME_SUFFIX}.
   * @param unixtime  date and time, in unix format (seconds of {@link System#currentTimeMillis()})
   * @param sb  append the generated name to this buffer
   */
  public static String makeDBBackupFilename(final int unixtime)
  {
    StringBuffer sb = new StringBuffer();
    sb.append(DBBACKUP_FILENAME_PREFIX);
    sb.append(DateFormat.format(DBBACKUP_FILENAME_TIMESTAMP, unixtime * 1000L));
    sb.append(DBBACKUP_FILENAME_SUFFIX);
    return sb.toString();
  }

  /**
   * Copy the current database file to a new backup.
   * Updates status fields in the database to indicate this,
   * as directed in the schema's comments.
   * The DB should be closed before calling this method.
   *
   * @param fromFilePath Full path to source database file; db must not be open.
   * @param toFilePath Full path to destination backup file
   * @param ctx  Context from which to obtain db info
   * @throws IllegalStateException if SDCard isn't mounted or isn't writeable
   * @throws IOException if an error occurs
   */
  public static void backupCurrentDB(Context ctx)
    throws IllegalStateException, IOException
  {
    /**
     * First, briefly open database, to get paths and update backup-related fields.
     */
    RDBAdapter db = new RDBOpenHelper(ctx);
    String fromFilePath = db.getFilenameFullPath();

    final String toFileDir = getDBBackupPath(ctx);
    if (toFileDir == null)
    {
      db.close();
      throw new IllegalStateException("not mounted or writeable");
    }
    StringBuffer toFilePath = new StringBuffer(toFileDir);
    // Make backup directory if needed
    {
      final String bkupDir = toFilePath.toString();
      File fdir = new File(bkupDir);
      if (! fdir.exists())
      {
        fdir.mkdirs();
        // TODO API lookup: retcode; throw ioexcepiton?
      }
      else if (! fdir.isDirectory())
      {
        throw new IOException("Not a directory: " + bkupDir);        
      }
    }
    toFilePath.append('/');
    final int thistime = (int) (System.currentTimeMillis() / 1000L);
    final String bkupFile = makeDBBackupFilename(thistime);
    toFilePath.append(bkupFile);

    /**
     * AppInfo table: Copy "this backup" info to "previous backup",
     * and update "this backup" info to the one we're doing now.
     */
    // First: Retrieve "previous backup" in case we need to save it.
    // If the keys aren't found, there is no previous (before most-recent) backup.
    int prevtime = -1, lasttime = -1;
    String prevBkupFile = null, lastBkupFile = null;
    AppInfo prevBkfile_rec = null, prevBktime_rec = null;
    try {
      prevBkfile_rec = new AppInfo(db, AppInfo.KEY_DB_BACKUP_PREVFILE);
      prevBkupFile = prevBkfile_rec.getValue();
      prevBktime_rec = new AppInfo(db, AppInfo.KEY_DB_BACKUP_PREVTIME);
      prevtime = Integer.parseInt(prevBktime_rec.getValue());
    }
    catch (RDBKeyNotFoundException e) { }
    catch (NumberFormatException e) { }

    // Next: Retrieve "this backup" info (most recent), and save it to "previous".
    // If the keys aren't found, there is no most-recent backup.
    AppInfo thisBkfile_rec = null, thisBktime_rec = null;
    try {
      thisBkfile_rec = new AppInfo(db, AppInfo.KEY_DB_BACKUP_THISFILE);
      lastBkupFile = thisBkfile_rec.getValue();
      thisBktime_rec = new AppInfo(db, AppInfo.KEY_DB_BACKUP_THISTIME);
      lasttime = Integer.parseInt(thisBktime_rec.getValue());

      if (prevBkfile_rec != null)
      {
        prevBkfile_rec.setValue(lastBkupFile);
        prevBkfile_rec.commit();
      } else {
        prevBkfile_rec = new AppInfo(AppInfo.KEY_DB_BACKUP_PREVFILE, lastBkupFile);
        prevBkfile_rec.insert(db);
      }
      if (prevBktime_rec != null)
      {
        prevBktime_rec.setValue(thisBktime_rec.getValue());
        prevBktime_rec.commit();
      } else {
        prevBktime_rec = new AppInfo(AppInfo.KEY_DB_BACKUP_PREVTIME, thisBktime_rec.getValue());
        prevBktime_rec.insert(db);
      }
    }
    catch (RDBKeyNotFoundException e) { }
    catch (NumberFormatException e) { }

    // Next: Set the current ones to now
    if (thisBkfile_rec != null)
    {
      thisBkfile_rec.setValue(bkupFile);
      thisBkfile_rec.commit();
    } else {
      thisBkfile_rec = new AppInfo(AppInfo.KEY_DB_BACKUP_THISFILE, bkupFile);
      thisBkfile_rec.insert(db);
    }
    if (thisBktime_rec != null)
    {
      thisBktime_rec.setValue(Integer.toString(thistime));
      thisBktime_rec.commit();      
    } else {
      thisBkfile_rec = new AppInfo(AppInfo.KEY_DB_BACKUP_THISTIME, Integer.toString(thistime));
      thisBkfile_rec.insert(db);      
    }

    db.close();
    db = null;
    // Note: Can't use prevBkfile_rec or prevBktime_rec beyond this point.

    /**
     * Do the actual backup.
     */
    try
    {
      FileUtils.copyFile(fromFilePath, toFilePath.toString(), false);
    } catch (IOException e)
    {
      /**
       * Backup failed; undo changes to backup-related AppInfo fields.
       * Then throw the exception.
       */
      db = new RDBOpenHelper(ctx);

      if (prevtime != -1)
      {
        AppInfo.insertOrUpdate(db, AppInfo.KEY_DB_BACKUP_PREVFILE, prevBkupFile);
        AppInfo.insertOrUpdate(db, AppInfo.KEY_DB_BACKUP_PREVTIME, Integer.toString(prevtime));        
      }
      if (lasttime != -1)
      {
        AppInfo.insertOrUpdate(db, AppInfo.KEY_DB_BACKUP_THISFILE, lastBkupFile);
        AppInfo.insertOrUpdate(db, AppInfo.KEY_DB_BACKUP_THISTIME, Integer.toString(lasttime));
      }

      // Done undoing AppInfo backup-status field changes.
      db.close();
      db = null;

      throw e;  // <--- Problem occurred ---
    }
  }

  /**
   * Given our app context, get the SD Card backup files list, if any
   * @param appc  app context, from {@link Context#getApplicationContext()}
   * @return list of filenames, or null if none found or if SD isn't mounted
   */
  public static ArrayList<String> getBkFiles(Context appc)
  {
    final String dirname = getDBBackupPath(appc);
    if (dirname == null)
      return null;
    return FileUtils.getFileNames(dirname, null, -1);
    }

}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.