/*
* Calendula - An assistant for personal medication management.
* Copyright (C) 2016 CITIUS - USC
*
* Calendula 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 software. If not, see <http://www.gnu.org/licenses/>.
*/
package es.usc.citius.servando.calendula.database;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.misc.TransactionManager;
import com.j256.ormlite.stmt.UpdateBuilder;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils;
import org.joda.time.LocalDate;
import java.sql.SQLException;
import java.util.List;
import java.util.concurrent.Callable;
import es.usc.citius.servando.calendula.database.migrationHelpers.LocalDateMigrationHelper;
import es.usc.citius.servando.calendula.persistence.DailyScheduleItem;
import es.usc.citius.servando.calendula.persistence.HomogeneousGroup;
import es.usc.citius.servando.calendula.persistence.HtmlCacheEntry;
import es.usc.citius.servando.calendula.persistence.Medicine;
import es.usc.citius.servando.calendula.persistence.Patient;
import es.usc.citius.servando.calendula.persistence.PickupInfo;
import es.usc.citius.servando.calendula.persistence.Prescription;
import es.usc.citius.servando.calendula.persistence.RepetitionRule;
import es.usc.citius.servando.calendula.persistence.Routine;
import es.usc.citius.servando.calendula.persistence.Schedule;
import es.usc.citius.servando.calendula.persistence.ScheduleItem;
/**
* Database helper class used to manage the creation and upgrading of your database. This class also usually provides
* the DAOs used by the other classes.
*/
public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
public static final String TAG = "DatabaseHelper";
// List of persisted classes to simplify table creation
public Class<?>[] persistedClasses = new Class<?>[]{
Routine.class,
Medicine.class,
Schedule.class,
ScheduleItem.class,
DailyScheduleItem.class,
Prescription.class,
// v8
HomogeneousGroup.class,
PickupInfo.class,
// v9
Patient.class,
// v10
HtmlCacheEntry.class
};
// name of the database file for our application
private static final String DATABASE_NAME = DB.DB_NAME;
// any time you make changes to your database objects, you may have to increase the database version
private static final int DATABASE_VERSION = 11;
// the DAO object we use to access the Medicines table
private Dao<Medicine, Long> medicinesDao = null;
// the DAO object we use to access the Routines table
private Dao<Routine, Long> routinesDao = null;
// the DAO object we use to access the Schedules table
private Dao<Schedule, Long> schedulesDao = null;
// the DAO object we use to access the ScheduleItems table
private Dao<ScheduleItem, Long> scheduleItemsDao = null;
// the DAO object we use to access the DailyScheduleItems table
private Dao<DailyScheduleItem, Long> dailyScheduleItemsDao = null;
// the DAO object we use to access the DailyScheduleItems table
private Dao<Prescription, Long> prescriptionsDao = null;
// the DAO object we use to access the HomogeneousGroups table
private Dao<HomogeneousGroup, Long> homogeneousGroupsDao = null;
// the DAO object we use to access the pcikupInfo table
private Dao<PickupInfo, Long> pickupInfoDao = null;
// the DAO object we use to access the patients table
private Dao<Patient, Long> patientDao = null;
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
/**
* This is called when the database is first created. Usually you should call createTable statements here to create
* the tables that will store your data.
*/
@Override
public void onCreate(SQLiteDatabase db, ConnectionSource connectionSource) {
try {
Log.i(DatabaseHelper.class.getName(), "onCreate");
for (Class<?> c : persistedClasses) {
Log.d(TAG, "Creating table for " + c.getSimpleName());
TableUtils.createTable(connectionSource, c);
}
createDefaultPatient();
} catch (SQLException e) {
Log.e(DatabaseHelper.class.getName(), "Can't create database", e);
throw new RuntimeException(e);
}
}
private Patient createDefaultPatient() throws SQLException {
// Create a default patient
Patient p = new Patient();
p.setName("Usuario");
p.setDefault(true);
getPatientDao().create(p);
return p;
}
/**
* This is called when your application is upgraded and it has a higher version number. This allows you to adjust
* the various data to match the new version number.
*/
@Override
public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int oldVersion, int newVersion) {
try {
Log.i(DatabaseHelper.class.getName(), "onUpgrade");
Log.d(DatabaseHelper.class.getName(), "OldVersion: " + oldVersion + ", newVersion: " + newVersion);
if (oldVersion < 6) {
oldVersion = 6;
}
switch (oldVersion + 1) {
case 7:
// migrate to iCal
migrateToICal();
case 8:
getMedicinesDao().executeRaw("ALTER TABLE Medicines ADD COLUMN hg INTEGER;");
// Add column scanned (boolean, INTEGER in SQLite) to schedules table
getSchedulesDao().executeRaw("ALTER TABLE Schedules ADD COLUMN Scanned INTEGER;");
// update schedules scanned value
TransactionManager.callInTransaction(getConnectionSource(), new Callable<Void>() {
@Override
public Void call() throws Exception {
// iterate over schedules and set Scanned to false
List<Schedule> schedules = getSchedulesDao().queryForAll();
for (Schedule s : schedules) {
s.setScanned(false);
s.save();
}
return null;
}
});
// Create HomogeneousGroup and PickupInfo tables
TableUtils.createTable(connectionSource, HomogeneousGroup.class);
TableUtils.createTable(connectionSource, PickupInfo.class);
case 9:
TableUtils.createTable(connectionSource, Patient.class);
migrateToMultiPatient();
case 10:
TableUtils.createTable(connectionSource, HtmlCacheEntry.class);
case 11:
//delete all html cache entries and change datatypes (bugfix)
TableUtils.dropTable(connectionSource, HtmlCacheEntry.class, true);
TableUtils.createTable(connectionSource, HtmlCacheEntry.class);
LocalDateMigrationHelper.migrateLocalDates(this);
}
} catch (Exception e) {
Log.e(DatabaseHelper.class.getName(), "Can't upgrade databases", e);
try {
Log.d(DatabaseHelper.class.getName(), "Will try to recreate db...");
dropAndCreateAllTables();
createDefaultPatient();
}catch (Exception ex){
throw new RuntimeException(e);
}
}
}
/**
* Method that migrate models to multi-user
*/
private void migrateToMultiPatient() throws SQLException {
// add patient column to routines, schedules and medicines
getRoutinesDao().executeRaw("ALTER TABLE Routines ADD COLUMN Patient INTEGER;");
getRoutinesDao().executeRaw("ALTER TABLE Medicines ADD COLUMN Patient INTEGER;");
getRoutinesDao().executeRaw("ALTER TABLE Schedules ADD COLUMN Patient INTEGER;");
getRoutinesDao().executeRaw("ALTER TABLE DailyScheduleItems ADD COLUMN Patient INTEGER;");
getRoutinesDao().executeRaw("ALTER TABLE DailyScheduleItems ADD COLUMN Date TEXT;");
Patient p = createDefaultPatient();
// SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences();
// prefs.edit().putLong(PatientDao.PREFERENCE_ACTIVE_PATIENT, p.id()).commit();
// Assign all routines to the default patient
UpdateBuilder<Routine, Long> rUpdateBuilder = getRoutinesDao().updateBuilder();
rUpdateBuilder.updateColumnValue(Routine.COLUMN_PATIENT, p.id());
rUpdateBuilder.update();
// Assign all schedules to the default patient
UpdateBuilder<Schedule, Long> sUpdateBuilder = getSchedulesDao().updateBuilder();
sUpdateBuilder.updateColumnValue(Schedule.COLUMN_PATIENT, p.id());
sUpdateBuilder.update();
// Assign all medicines to the default patient
UpdateBuilder<Medicine, Long> mUpdateBuilder = getMedicinesDao().updateBuilder();
mUpdateBuilder.updateColumnValue(Medicine.COLUMN_PATIENT, p.id());
mUpdateBuilder.update();
// Assign all DailyScheduleItems to the default patient, for today
UpdateBuilder<DailyScheduleItem, Long> siUpdateBuilder = getDailyScheduleItemsDao().updateBuilder();
siUpdateBuilder.updateColumnValue(DailyScheduleItem.COLUMN_PATIENT, p.id());
siUpdateBuilder.update();
// date formatter changes on v11, so we can no use LocalDatePersister here
String now = LocalDate.now().toString("ddMMYYYY");
String updateDateSql = "UPDATE DailyScheduleItems SET " + DailyScheduleItem.COLUMN_DATE + " = '" +now + "'";
getDailyScheduleItemsDao().executeRaw(updateDateSql);
}
/**
* Method that migrate schedules to the iCal format
*/
private void migrateToICal() throws SQLException {
getSchedulesDao().executeRaw("ALTER TABLE Schedules ADD COLUMN Rrule TEXT;");
getSchedulesDao().executeRaw("ALTER TABLE Schedules ADD COLUMN Start TEXT;");
getSchedulesDao().executeRaw("ALTER TABLE Schedules ADD COLUMN Starttime TEXT;");
getSchedulesDao().executeRaw("ALTER TABLE Schedules ADD COLUMN Dose REAL;");
getSchedulesDao().executeRaw("ALTER TABLE Schedules ADD COLUMN Type INTEGER;");
getSchedulesDao().executeRaw("ALTER TABLE Schedules ADD COLUMN Cycle TEXT;");
getDailyScheduleItemsDao().executeRaw(
"ALTER TABLE DailyScheduleItems ADD COLUMN Schedule INTEGER;");
getDailyScheduleItemsDao().executeRaw(
"ALTER TABLE DailyScheduleItems ADD COLUMN Time TEXT;");
// update schedules
TransactionManager.callInTransaction(getConnectionSource(), new Callable<Void>() {
@Override
public Void call() throws Exception {
// iterate over schedules and replace days[] with rrule
List<Schedule> schedules = getSchedulesDao().queryForAll();
Log.d(TAG, "Upgrade " + schedules.size() + " schedules");
for (Schedule s : schedules) {
if (s.rule() == null) {
s.setRepetition(new RepetitionRule(RepetitionRule.DEFAULT_ICAL_VALUE));
}
s.setDays(s.getLegacyDays());
if (s.allDaysSelected()) {
s.setType(Schedule.SCHEDULE_TYPE_EVERYDAY);
} else {
s.setType(Schedule.SCHEDULE_TYPE_SOMEDAYS);
}
s.setStart(LocalDate.now());
s.save();
}
return null;
}
});
}
/**
* Returns the Database Access Object (DAO) for our Medicines class. It will create it or just give the cached
* value.
*/
public Dao<Medicine, Long> getMedicinesDao() throws SQLException {
if (medicinesDao == null) {
medicinesDao = getDao(Medicine.class);
}
return medicinesDao;
}
/**
* Returns the Database Access Object (DAO) for our Routines class. It will create it or just give the cached
* value.
*/
public Dao<Routine, Long> getRoutinesDao() throws SQLException {
if (routinesDao == null) {
routinesDao = getDao(Routine.class);
}
return routinesDao;
}
/**
* Returns the Database Access Object (DAO) for our Schedules class. It will create it or just give the cached
* value.
*/
public Dao<Schedule, Long> getSchedulesDao() throws SQLException {
if (schedulesDao == null) {
schedulesDao = getDao(Schedule.class);
}
return schedulesDao;
}
/**
* Returns the Database Access Object (DAO) for our ScheduleItem class. It will create it or just give the cached
* value.
*/
public Dao<ScheduleItem, Long> getScheduleItemsDao() throws SQLException {
if (scheduleItemsDao == null) {
scheduleItemsDao = getDao(ScheduleItem.class);
}
return scheduleItemsDao;
}
/**
* Returns the Database Access Object (DAO) for our DailyScheduleItem class. It will create it or just give the cached
* value.
*/
public Dao<DailyScheduleItem, Long> getDailyScheduleItemsDao() throws SQLException {
if (dailyScheduleItemsDao == null) {
dailyScheduleItemsDao = getDao(DailyScheduleItem.class);
}
return dailyScheduleItemsDao;
}
/**
* Returns the Database Access Object (DAO) for our DailyScheduleItem class. It will create it or just give the cached
* value.
*/
public Dao<Prescription, Long> getPrescriptionsDao() throws SQLException {
if (prescriptionsDao == null) {
prescriptionsDao = getDao(Prescription.class);
}
return prescriptionsDao;
}
/**
* Returns the Database Access Object (DAO) for our HomogeneousGroup class. It will create it or just give the cached
* value.
*/
public Dao<HomogeneousGroup, Long> getHomogeneousGroupsDao() throws SQLException {
if (homogeneousGroupsDao == null) {
homogeneousGroupsDao = getDao(HomogeneousGroup.class);
}
return homogeneousGroupsDao;
}
/**
* Returns the Database Access Object (DAO) for our PickupInfo class. It will create it or just give the cached
* value.
*/
public Dao<PickupInfo, Long> getPickupInfosDao() throws SQLException {
if (pickupInfoDao == null) {
pickupInfoDao = getDao(PickupInfo.class);
}
return pickupInfoDao;
}
/**
* Returns the Database Access Object (DAO) for our User class. It will create it or just give the cached
* value.
*/
public Dao<Patient, Long> getPatientDao() throws SQLException {
if (patientDao == null) {
patientDao = getDao(Patient.class);
}
return patientDao;
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
public void dropAndCreateAllTables() {
Log.i(DatabaseHelper.class.getName(), "Dropping all tables...");
for (Class<?> c : persistedClasses) {
Log.d(TAG, "Dropping table " + c.getSimpleName());
try {
TableUtils.dropTable(connectionSource, c, true);
} catch (SQLException e) {
// ignore
Log.e(TAG, "Erro dropping table " + c.getSimpleName());
}
}
try {
Log.i(DatabaseHelper.class.getName(), "Creating tables...");
for (Class<?> c : persistedClasses) {
Log.d(TAG, "Creating table " + c.getSimpleName());
TableUtils.createTable(connectionSource, c);
}
} catch (SQLException e) {
Log.e(DatabaseHelper.class.getName(), "Can't recreate database", e);
throw new RuntimeException(e);
}
}
/**
* Close the database connections and clear any cached DAOs.
*/
@Override
public void close() {
super.close();
medicinesDao = null;
}
}