package com.lateensoft.pathfinder.toolkit.patching.v10; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.util.Log; import com.google.common.collect.Lists; import com.lateensoft.pathfinder.toolkit.dao.DataAccessException; import com.lateensoft.pathfinder.toolkit.db.Database; import com.lateensoft.pathfinder.toolkit.db.SQLiteDatabaseHelper; import com.lateensoft.pathfinder.toolkit.db.TableCreator; import com.lateensoft.pathfinder.toolkit.db.dao.table.*; import com.lateensoft.pathfinder.toolkit.model.character.PathfinderCharacter; import com.lateensoft.pathfinder.toolkit.model.party.Encounter; import com.lateensoft.pathfinder.toolkit.model.party.EncounterParticipant; import com.lateensoft.pathfinder.toolkit.patching.Patch; import com.lateensoft.pathfinder.toolkit.pref.GlobalPrefs; import com.lateensoft.pathfinder.toolkit.pref.Preferences; import org.jetbrains.annotations.Nullable; import roboguice.RoboGuice; import java.sql.SQLException; import java.util.List; /** * In version 10, the following changes require patching, if coming from v9 or earlier: * * - The Name column for characters was moved from the fluff table to the character table * * - Parties now use characters instead of special member objects, so all old members should now be characters * As a result: * * - New tables for encounters, party membership, and encounter participation were added. * - Party members need to be migrated to characters, and the partymembers table deleted * - The party table now does not use the IsEncounterParty column */ public class PostV9Patch extends Patch { private static final String TAG = PostV9Patch.class.getSimpleName(); private Preferences preferences; private Database database; private EncounterDAO<EncounterParticipant> encounterDAO; private CharacterModelDAO characterDao; private PartyMemberIdDAO partyMemberDao; private boolean errorsEncountered = false; public PostV9Patch(Context context) { super(context); preferences = RoboGuice.getInjector(context).getInstance(Preferences.class); database = RoboGuice.getInjector(context).getInstance(Database.class); EncounterParticipantDAO participantDAO = new EncounterParticipantDAO(context); encounterDAO = new EncounterDAO<EncounterParticipant>(context, participantDAO); characterDao = new CharacterModelDAO(context); partyMemberDao = new PartyMemberIdDAO(context); } @Override public boolean apply() { Log.i(TAG, "Applying v9 patches..."); moveCharacterNameColumn(); createNewTables(); convertEncounter(); /* Unfortunately we can't remove the IsEncounterParty Column, because Party table now has * character as a reference. Since DROP COLUMN is unsupported by SQLite, this would require * us to rebuild nearly the entire database. */ convertPartyMembers(); database.execSQL("DROP TABLE PartyMember;"); Log.i(TAG, "v9 patch complete"); return !errorsEncountered; } private void moveCharacterNameColumn() { database.execSQL("ALTER TABLE Character " + "ADD Name TEXT;"); Cursor cursor = database.query("FluffInfo", new String[]{"character_id", "Name"}, null); cursor.moveToFirst(); while(!cursor.isAfterLast()) { ContentValues contentValues = new ContentValues(); contentValues.put("Name", cursor.getString(1)); database.update("Character", contentValues, String.format("character_id=%d", cursor.getLong(0))); cursor.moveToNext(); } cursor.close(); try { new SQLiteDatabaseHelper(database).dropColumn(new TableCreator().createFluffInfo(), "FluffInfo", "Name"); } catch (SQLException e) { throw new IllegalStateException(e); } } private void createNewTables() { TableCreator tableCreator = new TableCreator(); database.execSQL(tableCreator.createEncounter()); database.execSQL(tableCreator.createEncounterParticipant()); database.execSQL(tableCreator.createPartyMembership()); } private void convertEncounter() { try { List<Long> encounterIds = findPartyIdsForSelector("InEncounter<>0"); for (Long encounterId : encounterIds) { String partySelector = String.format("party_id=%d", encounterId); Cursor partyNameCursor = database.query("Party", new String[] {"Name"}, partySelector); partyNameCursor.moveToFirst(); String encounterName = partyNameCursor.getString(0); partyNameCursor.close(); Encounter<EncounterParticipant> encounter = new Encounter<EncounterParticipant>(encounterName); List<PreV10PartyMember> preV10Members = getV9PartyMembersFromDatabase(String.format("party_id=%d", encounterId)); List<EncounterParticipant> v10Members = convertParticipants(preV10Members); encounter.addAll(v10Members); encounterDAO.add(encounter); preferences.put(GlobalPrefs.SELECTED_ENCOUNTER_ID, encounter.getId()); database.delete("Party", partySelector); } } catch (Exception e) { errorsEncountered = true; Log.e(TAG, "Failed to convert encounter", e); } } private List<Long> findPartyIdsForSelector(String selector) { List<Long> ids = Lists.newArrayList(); Cursor c = database.query("Party", new String[] {"party_id"}, selector); c.moveToFirst(); while (!c.isAfterLast()) { ids.add(c.getLong(0)); c.moveToNext(); } c.close(); return ids; } private List<PreV10PartyMember> getV9PartyMembersFromDatabase(String selector) { String[] columns = new String[] { "party_member_id", "Name", "Initiative", "AC", "Touch", "FlatFooted", "SpellResist", "DamageReduction", "CMD", "FortSave", "ReflexSave", "WillSave", "BluffSkillBonus", "DisguiseSkillBonus", "PerceptionSkillBonus", "SenseMotiveSkillBonus", "StealthSkillBonus", "RolledValue" }; Cursor c = database.query("PartyMember", columns, selector); List<PreV10PartyMember> partyMembers = Lists.newArrayList(); c.moveToFirst(); while (!c.isAfterLast()) { partyMembers.add(buildFromCursor(c)); c.moveToNext(); } c.close(); return partyMembers; } protected PreV10PartyMember buildFromCursor(Cursor c) { PreV10PartyMember member = new PreV10PartyMember(); member.name = c.getString(c.getColumnIndex("Name")); member.initiative = c.getInt(c.getColumnIndex("Initiative")); member.AC = c.getInt(c.getColumnIndex("AC")); member.touch = c.getInt(c.getColumnIndex("Touch")); member.flatFooted = c.getInt(c.getColumnIndex("FlatFooted")); member.spellResist = c.getInt(c.getColumnIndex("SpellResist")); member.damageReduction = c.getInt(c.getColumnIndex("DamageReduction")); member.CMD = c.getInt(c.getColumnIndex("CMD")); member.fortSave = c.getInt(c.getColumnIndex("FortSave")); member.reflexSave = c.getInt(c.getColumnIndex("ReflexSave")); member.willSave = c.getInt(c.getColumnIndex("WillSave")); member.bluffSkillBonus = c.getInt(c.getColumnIndex("BluffSkillBonus")); member.disguiseSkillBonus = c.getInt(c.getColumnIndex("DisguiseSkillBonus")); member.perceptionSkillBonus = c.getInt(c.getColumnIndex("PerceptionSkillBonus")); member.senseMotiveSkillBonus = c.getInt(c.getColumnIndex("SenseMotiveSkillBonus")); member.stealthSkillBonus = c.getInt(c.getColumnIndex("StealthSkillBonus")); member.lastRolledValue = c.getInt(c.getColumnIndex("RolledValue")); return member; } private List<EncounterParticipant> convertParticipants(List<PreV10PartyMember> preV10Members) { List<EncounterParticipant> participants = Lists.newArrayListWithCapacity(preV10Members.size()); for (PreV10PartyMember member : preV10Members) { participants.add(PreV10PartyMemberConverter.convertEncounterParticipant(member)); } return participants; } private void convertPartyMembers() { try { List<Long> partyIds = findPartyIdsForSelector(null); for (Long partyId : partyIds) { try { List<PreV10PartyMember> preV10Members = getV9PartyMembersFromDatabase(String.format("party_id=%d", partyId)); List<PathfinderCharacter> members = convertMembers(preV10Members); for (PathfinderCharacter character : members) { characterDao.add(character); partyMemberDao.add(partyId, character.getId()); } } catch (DataAccessException e) { errorsEncountered = true; Log.e(TAG, "Error migrating party " + partyId, e); } } } catch (Exception e) { errorsEncountered = true; Log.e(TAG, "Failed to convert parties", e); } } private List<PathfinderCharacter> convertMembers(List<PreV10PartyMember> preV10Members) { List<PathfinderCharacter> participants = Lists.newArrayListWithCapacity(preV10Members.size()); for (PreV10PartyMember member : preV10Members) { participants.add(PreV10PartyMemberConverter.convertPartyMember(member)); } return participants; } @Nullable @Override public Patch getNext() { return null; } }