Database revolution
- added in fitbitData an interface for keeping track of date - removed annoying IOException, now will write a LOG - same thing in sensor - fixed main and adapted to new Database system - Database now has Local or Remote (to implement)
This commit is contained in:
@@ -1,190 +0,0 @@
|
||||
package support;
|
||||
|
||||
import device.Fitbit;
|
||||
import device.fitbitdata.Sleep;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Classe che si connette al database e permette di aggiornare i dati in modo automatico tramite dei runnable
|
||||
*/
|
||||
public class Database {
|
||||
|
||||
/**
|
||||
* Un logger per scrivere a console eventuali errori o informazioni
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Database.class);
|
||||
|
||||
/**
|
||||
* Il percorso di dove trovare il database, strutturato in: <interfaccia>:<implementazione>:<percorso vero e proprio>
|
||||
*/
|
||||
public static final String DB_LOCATION = "jdbc:sqlite:";
|
||||
|
||||
/**
|
||||
* Il nome del database (aka il nome del file)
|
||||
*/
|
||||
public static final String DB_NAME = "user_data.db";
|
||||
|
||||
/**
|
||||
* Un calendario, visto che ci serve sapere quando inseriamo i dati
|
||||
*/
|
||||
private static final Calendar CALENDAR = Calendar.getInstance();
|
||||
|
||||
/**
|
||||
* Una costante che indica quanti millisecondi ci sono in un minuto (utile per le conversioni)
|
||||
*/
|
||||
private static final int MINUTES_TO_MILLISEC = 60000;
|
||||
|
||||
/**
|
||||
* L'unica istanza del database (dato che e' una classe Singleton)
|
||||
*/
|
||||
private static Database instance;
|
||||
|
||||
/**
|
||||
* La connessione al database
|
||||
*/
|
||||
private final Connection conn;
|
||||
|
||||
/**
|
||||
* Crea una connessione al Database specificato in DB_LOCATION e con il nome DB_NAME.
|
||||
* Se il Database non esiste lo crea e inizializza anche delle tabelle:
|
||||
* <ul>
|
||||
* <li>'total' contiene i dati utente giorno per giorno.</li>
|
||||
* <li>'heart' battito + orario</li>
|
||||
* <li>'sleep' inizio + fine + durata</li>
|
||||
* </ul>
|
||||
* @throws SQLException se qualcosa e' andato storto
|
||||
*/
|
||||
private Database() throws SQLException {
|
||||
CALENDAR.setTimeInMillis(System.currentTimeMillis());
|
||||
conn = DriverManager.getConnection(DB_LOCATION + DB_NAME);
|
||||
Statement statement = conn.createStatement();
|
||||
statement.execute("CREATE TABLE IF NOT EXISTS total (day DATE PRIMARY KEY, sleep_time INTEGER, heart_rate DOUBLE, steps INTEGER)");
|
||||
statement.execute("CREATE TABLE IF NOT EXISTS heart (day_hour DATE PRIMARY KEY, rate DOUBLE)");
|
||||
statement.execute("CREATE TABLE IF NOT EXISTS sleep (sleep_start DATE PRIMARY KEY, sleep_end DATE, duration INTEGER)");
|
||||
}
|
||||
|
||||
/**
|
||||
* Crea una connessione al Database specificato in DB_LOCATION e con il nome DB_NAME.
|
||||
* Se il Database non esiste lo crea e inizializza anche delle tabelle:
|
||||
* <ul>
|
||||
* <li>'total' contiene i dati utente giorno per giorno.</li>
|
||||
* <li>'heart' battito + orario</li>
|
||||
* <li>'sleep' inizio + fine + durata</li>
|
||||
* </ul>
|
||||
* <br>
|
||||
* L'implementazione e' synchronized (si spera thread safe)
|
||||
*/
|
||||
public synchronized static Database getInstance() {
|
||||
try {
|
||||
instance = instance==null? new Database():instance;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prendi il Runnable che automaticamente gestisce l'inserimento dei dati orari (per ora solo il battito cardiaco)<br>
|
||||
* <br>
|
||||
* Il runnable e' synchronized (si spera thread safe)
|
||||
* @param fitbit la classe che contiene i dati aggiornati
|
||||
* @return un Runnable
|
||||
*/
|
||||
public Runnable insertHourlyData(Fitbit fitbit) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public synchronized void run() {
|
||||
boolean notInterrupted = true;
|
||||
boolean retry = false;
|
||||
double heartRate = 0;
|
||||
Timestamp now = null;
|
||||
|
||||
while (notInterrupted) {
|
||||
CALENDAR.setTimeInMillis(System.currentTimeMillis());
|
||||
try {
|
||||
wait((retry ? 1:58-CALENDAR.get(Calendar.MINUTE)) * MINUTES_TO_MILLISEC);
|
||||
CALENDAR.setTimeInMillis(System.currentTimeMillis());
|
||||
if (retry == false) {
|
||||
now = new Timestamp(CALENDAR.getTimeInMillis());
|
||||
heartRate = fitbit.getHeartRate(60);
|
||||
}
|
||||
|
||||
conn.createStatement().execute("INSERT INTO heart (day_hour, rate) VALUES ( ' " + now + " ', '" + heartRate + "')");
|
||||
LOG.info(CALENDAR.getTime() + " > Ho inserito i dati orari "+now);
|
||||
retry = false;
|
||||
wait(MINUTES_TO_MILLISEC);
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
notInterrupted = false;
|
||||
} catch (Exception e) {
|
||||
LOG.error(CALENDAR.getTime() + " " + e.getLocalizedMessage() + " > Non e' stato possibile aggingere i dati orari al database, riprovo fra un minuto "+now);
|
||||
retry = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Prendi il Runnable che automaticamente gestisce l'inserimento dei dati giornalieri<br>
|
||||
* <br>
|
||||
* Il runnable e' synchronized (si spera thread safe)
|
||||
* @param fitbit la classe che contiene i dati aggiornati
|
||||
* @return un Runnable
|
||||
*/
|
||||
public Runnable insertDailyData(Fitbit fitbit) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public synchronized void run() {
|
||||
boolean notInterrupted = true;
|
||||
boolean retry = false;
|
||||
double heartRate = 0;
|
||||
long sleepTime = 0;
|
||||
long steps = 0;
|
||||
List<Sleep.SleepData> sleepDatas = null;
|
||||
Timestamp now = null;
|
||||
|
||||
while (notInterrupted) {
|
||||
CALENDAR.setTimeInMillis(System.currentTimeMillis());
|
||||
try {
|
||||
wait((retry? 1:(22-CALENDAR.get(Calendar.HOUR))*60) * MINUTES_TO_MILLISEC);
|
||||
CALENDAR.setTimeInMillis(System.currentTimeMillis());
|
||||
if (retry == false) {
|
||||
heartRate = fitbit.getHeartRate(60 * 24);
|
||||
sleepTime = fitbit.getHoursSleep();
|
||||
sleepDatas = fitbit.getDetailedSleep();
|
||||
steps = fitbit.getSteps();
|
||||
now = new Timestamp(CALENDAR.getTimeInMillis());
|
||||
}
|
||||
|
||||
conn.createStatement().execute("INSERT INTO total (day, sleep_time, heart_rate, steps) VALUES ( '" + now + "', '" + sleepTime + "', '" + heartRate + "', '" + steps + "' )");
|
||||
for (Sleep.SleepData data : sleepDatas)
|
||||
conn.createStatement().execute("INSERT INTO total (sleep_start, sleep_end, duration) VALUES ( '" + data.start_date + "', '" + data.end_date + "', '" + data.duration + "' )");
|
||||
LOG.info(CALENDAR.getTime() + " > Ho inserito i dati giornalieri");
|
||||
retry = false;
|
||||
wait(MINUTES_TO_MILLISEC);
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
notInterrupted = false;
|
||||
} catch (Exception e) {
|
||||
LOG.error(CALENDAR.getTime() + " " + e.getLocalizedMessage() + " > Non e' stato possibile aggingere i dati della giornata al database, riprovo fra un minuto");
|
||||
retry = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
public ResultSet getDataFromDatabase(String sql) throws SQLException {
|
||||
Statement statement = conn.createStatement();
|
||||
ResultSet rs = statement.executeQuery(sql);
|
||||
return rs;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -83,7 +83,6 @@ public class Rest {
|
||||
} catch (Exception e) {
|
||||
LOG.error("PUT: " + e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
156
src/main/java/support/database/Database.java
Normal file
156
src/main/java/support/database/Database.java
Normal file
@@ -0,0 +1,156 @@
|
||||
package support.database;
|
||||
|
||||
import device.Fitbit;
|
||||
import device.fitbitdata.HeartRate;
|
||||
import device.fitbitdata.Sleep;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.List;
|
||||
|
||||
public interface Database {
|
||||
|
||||
/**
|
||||
* Un logger per scrivere a console eventuali errori o informazioni
|
||||
*/
|
||||
Logger LOG = LoggerFactory.getLogger("DB");
|
||||
|
||||
/**
|
||||
* Una costante che indica quanti millisecondi ci sono in un minuto (utile per le conversioni)
|
||||
*/
|
||||
int MILLISEC_IN_MINUTE = 60000;
|
||||
|
||||
/**
|
||||
* Dice solamente se e' possibile collegarsi al database e se si possono fare delle query
|
||||
* @return vero se si pu' falso in altri casi.
|
||||
*/
|
||||
boolean isReachable();
|
||||
|
||||
/**
|
||||
* Inserisce nuovi dati del paziente nel database.
|
||||
* @param dateMilliSec la data che si vuole inserire in millisec
|
||||
* @param heartRate il battito cardiaco
|
||||
* @return vero se ha inserito o i dati esistevano gia' falso se non ce l'ha fatta
|
||||
*/
|
||||
boolean updateHeart(long dateMilliSec, double heartRate);
|
||||
/**
|
||||
* Inserisce nuovi dati del paziente nel database.
|
||||
* @param dateStartSleep la data che si vuole inserire in millisec
|
||||
* @param duration per quanto e' durato il sonno
|
||||
* @return vero se ha inserito o i dati esistevano gia' falso se non ce l'ha fatta
|
||||
*/
|
||||
boolean updateSleep(long dateStartSleep, long duration);
|
||||
/**
|
||||
* Inserisce nuovi dati del paziente nel database.
|
||||
* @param dateMilliSec la data che si vuole inserire in millisec
|
||||
* @param steps i passi fatti
|
||||
* @return vero se ha inserito o i dati esistevano gia' falso se non ce l'ha fatta
|
||||
*/
|
||||
boolean updateSteps(long dateMilliSec, long steps);
|
||||
|
||||
/**
|
||||
* Riceve i dati del cuore dal giorno selezionato fino ad oggi
|
||||
* @param days quanti giorni devono esser considerati
|
||||
* @return una lista dei battiti cardiaci degli ultimi X giorni (ordinati da oggi al giorno X)
|
||||
*/
|
||||
List<HeartRate> getHeartDataOfLast(int days);
|
||||
|
||||
/**
|
||||
* Prendi il Thread che automaticamente gestisce l'inserimento dei dati orari (per ora solo il battito cardiaco)<br>
|
||||
* Se per caso c'e' un fallimento riprova ad inserire i dati ogni x minuti, indicati dal terzo parametro<br>
|
||||
* @param database il database in cui inserirlo
|
||||
* @param fitbit la classe che contiene i dati aggiornati
|
||||
* @param retryMinutes ogni quanti minuti deve riprovare ad inviare la richiesta
|
||||
* @return un Thread
|
||||
*/
|
||||
static Thread insertHourlyDataIn(Database database, Fitbit fitbit, int retryMinutes) {
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public synchronized void run() {
|
||||
LOG.info("Aggiornamento orario iniziato");
|
||||
try {
|
||||
boolean retry;
|
||||
long now = System.currentTimeMillis();
|
||||
double heartRate = 30;//fitbit.getHeartRate(60);
|
||||
do {
|
||||
retry = !database.updateHeart(now, heartRate);
|
||||
LOG.info("Aggiornamento " + (!retry ? "riuscito" : "fallito, riprovo fra " + retryMinutes + " minuti"));
|
||||
if (retry)
|
||||
wait(retryMinutes * MILLISEC_IN_MINUTE);
|
||||
} while(retry);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Aggiornamento interrotto");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return getThreadStartingEach(runnable, "update-hourly-data", 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prendi il Thread che automaticamente gestisce l'inserimento dei dati giornalieri<br>
|
||||
* Se per caso c'e' un fallimento riprova ad inserire i dati ogni x minuti, indicati dal terzo parametro<br>
|
||||
* @param database il database in cui inserirlo
|
||||
* @param fitbit la classe che contiene i dati aggiornati
|
||||
* @param retryMinutes ogni quanti minuti deve riprovare ad inviare la richiesta
|
||||
* @return un Thread da far partire
|
||||
*/
|
||||
static Thread insertDailyData(Database database, Fitbit fitbit, int retryMinutes) {
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public synchronized void run() {
|
||||
LOG.info("Aggiornamento giornaliero iniziato");
|
||||
try {
|
||||
boolean retry;
|
||||
long steps = fitbit.getSteps();
|
||||
List<Sleep.SleepData> sleepDatas = fitbit.getDetailedSleep();
|
||||
long now = System.currentTimeMillis();
|
||||
do {
|
||||
retry = !database.updateSteps(now, steps);
|
||||
for (Sleep.SleepData data : sleepDatas)
|
||||
retry = retry && !database.updateSleep(data.start_date, data.duration);
|
||||
|
||||
LOG.info("Aggiornamento " + (!retry ? "riuscito" : "fallito, riprovo fra " + retryMinutes + " minuti"));
|
||||
if (retry)
|
||||
wait(retryMinutes * MILLISEC_IN_MINUTE);
|
||||
} while (retry);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Aggiornamento interrotto");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return getThreadStartingEach(runnable, "update-daily-data", 24*60);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restuisce un thread che se fatto partire, esegue il runnable in un sub-thread ogni X minuti<br>
|
||||
* Il sotto thread lanciato avra' lo stesso nome, ma con un trattino e la data di lancio a seguito<br>
|
||||
* Se il thread viene interrotto non fa piu' partire il runnable e si ferma
|
||||
* @param runnable il runnable da lanciare
|
||||
* @param threadName il nome da dare al thread
|
||||
* @param minutes i minuti da aspettare, se negativi o 0 ritorna null
|
||||
*/
|
||||
static Thread getThreadStartingEach(final Runnable runnable, String threadName, int minutes) {
|
||||
if(minutes<1)
|
||||
return null;
|
||||
|
||||
return new Thread(new Runnable() {
|
||||
@Override
|
||||
public synchronized void run() {
|
||||
boolean notInterrupted = true;
|
||||
do {
|
||||
try {
|
||||
wait(minutes * MILLISEC_IN_MINUTE);
|
||||
Thread thread = new Thread(runnable, threadName + "-" + new Timestamp(System.currentTimeMillis()));
|
||||
thread.start();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
notInterrupted = false;
|
||||
}
|
||||
} while (notInterrupted);
|
||||
}
|
||||
}, threadName);
|
||||
}
|
||||
}
|
||||
106
src/main/java/support/database/LocalDB.java
Normal file
106
src/main/java/support/database/LocalDB.java
Normal file
@@ -0,0 +1,106 @@
|
||||
package support.database;
|
||||
|
||||
import device.fitbitdata.HeartRate;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Classe che utilizza un database sqlite che contiene le seguenti tabelle:<br>
|
||||
* <ul>
|
||||
* <li>'heart' battito + orario</li>
|
||||
* <li>'sleep' inizio + durata</li>
|
||||
* <li>'steps' data + passi.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class LocalDB implements Database {
|
||||
|
||||
/**
|
||||
* Il percorso di dove trovare il database, strutturato in: <interfaccia>:<implementazione>:<percorso vero e proprio>
|
||||
*/
|
||||
public static final String DB_LOCATION = "jdbc:sqlite:";
|
||||
|
||||
/**
|
||||
* Il nome del database (aka il nome del file)
|
||||
*/
|
||||
public static final String DB_NAME = "user_data.db";
|
||||
|
||||
/**
|
||||
* La connessione al database
|
||||
*/
|
||||
private final Connection conn;
|
||||
|
||||
/**
|
||||
* Crea una connessione al Database specificato in DB_LOCATION e con il nome DB_NAME.
|
||||
* Se il Database non esiste lo crea e inizializza anche delle tabelle:
|
||||
* <ul>
|
||||
* <li>'heart' battito + orario</li>
|
||||
* <li>'sleep' inizio + durata</li>
|
||||
* <li>'step' data + passi.</li>
|
||||
* </ul>
|
||||
* @throws SQLException se qualcosa e' andato storto
|
||||
*/
|
||||
public LocalDB() throws SQLException {
|
||||
conn = DriverManager.getConnection(DB_LOCATION + DB_NAME);
|
||||
Statement statement = conn.createStatement();
|
||||
statement.execute("CREATE TABLE IF NOT EXISTS heart (day DATE PRIMARY KEY, rate DOUBLE)");
|
||||
statement.execute("CREATE TABLE IF NOT EXISTS sleep (sleep_start DATE PRIMARY KEY, duration INTEGER)");
|
||||
statement.execute("CREATE TABLE IF NOT EXISTS steps (day DATE PRIMARY KEY, steps INTEGER)");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReachable() { return conn!=null; }
|
||||
|
||||
@Override
|
||||
public boolean updateHeart(long dateMilliSec, double heartRate) {
|
||||
return query("INSERT INTO heart (day, rate) VALUES ( ' " + new Timestamp(dateMilliSec) + " ', '" + heartRate + "')");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateSleep(long dateStartSleep, long duration) {
|
||||
return query("INSERT INTO sleep (sleep_start, duration) VALUES ( ' " + new Timestamp(dateStartSleep) + " ', '" + duration + "')");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateSteps(long dateMilliSec, long steps) {
|
||||
return query("INSERT INTO steps (day, steps) VALUES ( ' " + new Timestamp(dateMilliSec) + " ', '" + steps + "')");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HeartRate> getHeartDataOfLast(int days) {
|
||||
try {
|
||||
int dayToSubtract = 15;
|
||||
// Calendar calendar = Calendar.getInstance();
|
||||
// calendar.setTimeInMillis(System.currentTimeMillis());
|
||||
// calendar.add(Calendar.DATE, -dayToSubtract);
|
||||
// long time = calendar.getTimeInMillis();
|
||||
long time = System.currentTimeMillis() - (dayToSubtract * 24 * 60 * 1000); // meno 24 giorni per 60 secondi per 100 millisec
|
||||
|
||||
ResultSet result = conn.createStatement().executeQuery("SELECT * FROM heart WHERE day>='" + new Timestamp(time) + "'");
|
||||
List<HeartRate> list = new LinkedList<>();
|
||||
|
||||
while(result.next()) {
|
||||
HeartRate rate = new HeartRate();
|
||||
rate.setAverage(result.getDouble("rate"));
|
||||
rate.setDate(result.getDate("day").getTime());
|
||||
|
||||
list.add(rate);
|
||||
}
|
||||
return list;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean query(String sql) {
|
||||
try {
|
||||
conn.createStatement().execute(sql);
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
LOG.error(e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
39
src/main/java/support/database/RemoteDB.java
Normal file
39
src/main/java/support/database/RemoteDB.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package support.database;
|
||||
|
||||
import device.fitbitdata.HeartRate;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
// TODO implement
|
||||
public class RemoteDB implements Database {
|
||||
|
||||
public RemoteDB(String url) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReachable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateHeart(long dateMilliSec, double heartRate) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateSleep(long dateStartSleep, long duration) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateSteps(long dateMilliSec, long steps) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HeartRate> getHeartDataOfLast(int days) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user