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
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";
|
|
}
|
|
}
|
|
}
|
|
|