You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

354 lines
13 KiB

/**
* DailyNotificationMigration.java
*
* Migration utilities for transitioning from SharedPreferences to SQLite
* Handles data migration while preserving existing notification data
*
* @author Matthew Raymer
* @version 1.0.0
*/
package com.timesafari.dailynotification;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
/**
* Handles migration from SharedPreferences to SQLite database
*
* This class provides utilities to:
* - Migrate existing notification data from SharedPreferences
* - Preserve all existing notification content during transition
* - Provide backward compatibility during migration period
* - Validate migration success
*/
public class DailyNotificationMigration {
private static final String TAG = "DailyNotificationMigration";
private static final String PREFS_NAME = "DailyNotificationPrefs";
private static final String KEY_NOTIFICATIONS = "notifications";
private static final String KEY_SETTINGS = "settings";
private static final String KEY_LAST_FETCH = "last_fetch";
private static final String KEY_ADAPTIVE_SCHEDULING = "adaptive_scheduling";
private final Context context;
private final DailyNotificationDatabase database;
private final Gson gson;
/**
* Constructor
*
* @param context Application context
* @param database SQLite database instance
*/
public DailyNotificationMigration(Context context, DailyNotificationDatabase database) {
this.context = context;
this.database = database;
this.gson = new Gson();
}
/**
* Perform complete migration from SharedPreferences to SQLite
*
* @return true if migration was successful
*/
public boolean migrateToSQLite() {
try {
Log.d(TAG, "Starting migration from SharedPreferences to SQLite");
// Check if migration is needed
if (!isMigrationNeeded()) {
Log.d(TAG, "Migration not needed - SQLite already up to date");
return true;
}
// Get writable database
SQLiteDatabase db = database.getWritableDatabase();
// Start transaction for atomic migration
db.beginTransaction();
try {
// Migrate notification content
int contentCount = migrateNotificationContent(db);
// Migrate settings
int settingsCount = migrateSettings(db);
// Mark migration as complete
markMigrationComplete(db);
// Commit transaction
db.setTransactionSuccessful();
Log.i(TAG, String.format("Migration completed successfully: %d notifications, %d settings",
contentCount, settingsCount));
return true;
} catch (Exception e) {
Log.e(TAG, "Error during migration transaction", e);
db.endTransaction();
return false;
} finally {
db.endTransaction();
}
} catch (Exception e) {
Log.e(TAG, "Error during migration", e);
return false;
}
}
/**
* Check if migration is needed
*
* @return true if migration is required
*/
private boolean isMigrationNeeded() {
try {
// Check if SharedPreferences has data
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
String notificationsJson = prefs.getString(KEY_NOTIFICATIONS, "[]");
// Check if SQLite already has data
SQLiteDatabase db = database.getReadableDatabase();
android.database.Cursor cursor = db.rawQuery(
"SELECT COUNT(*) FROM " + DailyNotificationDatabase.TABLE_NOTIF_CONTENTS, null);
int sqliteCount = 0;
if (cursor.moveToFirst()) {
sqliteCount = cursor.getInt(0);
}
cursor.close();
// Migration needed if SharedPreferences has data but SQLite doesn't
boolean hasPrefsData = !notificationsJson.equals("[]") && !notificationsJson.isEmpty();
boolean needsMigration = hasPrefsData && sqliteCount == 0;
Log.d(TAG, String.format("Migration check: prefs_data=%s, sqlite_count=%d, needed=%s",
hasPrefsData, sqliteCount, needsMigration));
return needsMigration;
} catch (Exception e) {
Log.e(TAG, "Error checking migration status", e);
return false;
}
}
/**
* Migrate notification content from SharedPreferences to SQLite
*
* @param db SQLite database instance
* @return Number of notifications migrated
*/
private int migrateNotificationContent(SQLiteDatabase db) {
try {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
String notificationsJson = prefs.getString(KEY_NOTIFICATIONS, "[]");
if (notificationsJson.equals("[]") || notificationsJson.isEmpty()) {
Log.d(TAG, "No notification content to migrate");
return 0;
}
// Parse JSON to List<NotificationContent>
Type type = new TypeToken<ArrayList<NotificationContent>>(){}.getType();
List<NotificationContent> notifications = gson.fromJson(notificationsJson, type);
int migratedCount = 0;
for (NotificationContent notification : notifications) {
try {
// Create ContentValues for notif_contents table
ContentValues values = new ContentValues();
values.put(DailyNotificationDatabase.COL_CONTENTS_SLOT_ID, notification.getId());
values.put(DailyNotificationDatabase.COL_CONTENTS_PAYLOAD_JSON,
gson.toJson(notification));
values.put(DailyNotificationDatabase.COL_CONTENTS_FETCHED_AT,
notification.getFetchedAt());
// ETag is null for migrated data
values.putNull(DailyNotificationDatabase.COL_CONTENTS_ETAG);
// Insert into notif_contents table
long rowId = db.insert(DailyNotificationDatabase.TABLE_NOTIF_CONTENTS, null, values);
if (rowId != -1) {
migratedCount++;
Log.d(TAG, "Migrated notification: " + notification.getId());
} else {
Log.w(TAG, "Failed to migrate notification: " + notification.getId());
}
} catch (Exception e) {
Log.e(TAG, "Error migrating notification: " + notification.getId(), e);
}
}
Log.i(TAG, "Migrated " + migratedCount + " notifications to SQLite");
return migratedCount;
} catch (Exception e) {
Log.e(TAG, "Error migrating notification content", e);
return 0;
}
}
/**
* Migrate settings from SharedPreferences to SQLite
*
* @param db SQLite database instance
* @return Number of settings migrated
*/
private int migrateSettings(SQLiteDatabase db) {
try {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
int migratedCount = 0;
// Migrate last_fetch timestamp
long lastFetch = prefs.getLong(KEY_LAST_FETCH, 0);
if (lastFetch > 0) {
ContentValues values = new ContentValues();
values.put(DailyNotificationDatabase.COL_CONFIG_K, KEY_LAST_FETCH);
values.put(DailyNotificationDatabase.COL_CONFIG_V, String.valueOf(lastFetch));
long rowId = db.insert(DailyNotificationDatabase.TABLE_NOTIF_CONFIG, null, values);
if (rowId != -1) {
migratedCount++;
Log.d(TAG, "Migrated last_fetch setting");
}
}
// Migrate adaptive_scheduling setting
boolean adaptiveScheduling = prefs.getBoolean(KEY_ADAPTIVE_SCHEDULING, false);
ContentValues values = new ContentValues();
values.put(DailyNotificationDatabase.COL_CONFIG_K, KEY_ADAPTIVE_SCHEDULING);
values.put(DailyNotificationDatabase.COL_CONFIG_V, String.valueOf(adaptiveScheduling));
long rowId = db.insert(DailyNotificationDatabase.TABLE_NOTIF_CONFIG, null, values);
if (rowId != -1) {
migratedCount++;
Log.d(TAG, "Migrated adaptive_scheduling setting");
}
Log.i(TAG, "Migrated " + migratedCount + " settings to SQLite");
return migratedCount;
} catch (Exception e) {
Log.e(TAG, "Error migrating settings", e);
return 0;
}
}
/**
* Mark migration as complete in the database
*
* @param db SQLite database instance
*/
private void markMigrationComplete(SQLiteDatabase db) {
try {
ContentValues values = new ContentValues();
values.put(DailyNotificationDatabase.COL_CONFIG_K, "migration_complete");
values.put(DailyNotificationDatabase.COL_CONFIG_V, String.valueOf(System.currentTimeMillis()));
db.insert(DailyNotificationDatabase.TABLE_NOTIF_CONFIG, null, values);
Log.d(TAG, "Migration marked as complete");
} catch (Exception e) {
Log.e(TAG, "Error marking migration complete", e);
}
}
/**
* Validate migration success
*
* @return true if migration was successful
*/
public boolean validateMigration() {
try {
SQLiteDatabase db = database.getReadableDatabase();
// Check if migration_complete flag exists
android.database.Cursor cursor = db.query(
DailyNotificationDatabase.TABLE_NOTIF_CONFIG,
new String[]{DailyNotificationDatabase.COL_CONFIG_V},
DailyNotificationDatabase.COL_CONFIG_K + " = ?",
new String[]{"migration_complete"},
null, null, null
);
boolean migrationComplete = cursor.moveToFirst();
cursor.close();
if (!migrationComplete) {
Log.w(TAG, "Migration validation failed - migration_complete flag not found");
return false;
}
// Check if we have notification content
cursor = db.rawQuery(
"SELECT COUNT(*) FROM " + DailyNotificationDatabase.TABLE_NOTIF_CONTENTS, null);
int contentCount = 0;
if (cursor.moveToFirst()) {
contentCount = cursor.getInt(0);
}
cursor.close();
Log.i(TAG, "Migration validation successful - " + contentCount + " notifications in SQLite");
return true;
} catch (Exception e) {
Log.e(TAG, "Error validating migration", e);
return false;
}
}
/**
* Get migration statistics
*
* @return Migration statistics string
*/
public String getMigrationStats() {
try {
SQLiteDatabase db = database.getReadableDatabase();
// Count notifications
android.database.Cursor cursor = db.rawQuery(
"SELECT COUNT(*) FROM " + DailyNotificationDatabase.TABLE_NOTIF_CONTENTS, null);
int notificationCount = 0;
if (cursor.moveToFirst()) {
notificationCount = cursor.getInt(0);
}
cursor.close();
// Count settings
cursor = db.rawQuery(
"SELECT COUNT(*) FROM " + DailyNotificationDatabase.TABLE_NOTIF_CONFIG, null);
int settingsCount = 0;
if (cursor.moveToFirst()) {
settingsCount = cursor.getInt(0);
}
cursor.close();
return String.format("Migration stats: %d notifications, %d settings",
notificationCount, settingsCount);
} catch (Exception e) {
Log.e(TAG, "Error getting migration stats", e);
return "Migration stats: Error retrieving data";
}
}
}