davidmigloz/go-bees

View on GitHub
app/src/main/java/com/davidmiguel/gobees/data/source/local/GoBeesLocalDataSource.java

Summary

Maintainability
D
2 days
Test Coverage
/*
 * GoBees
 * Copyright (c) 2016 - 2017 David Miguel Lozano
 *
 * This program 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
 * 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 program. If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
 */

package com.davidmiguel.gobees.data.source.local;

import android.support.annotation.NonNull;

import com.davidmiguel.gobees.data.model.Apiary;
import com.davidmiguel.gobees.data.model.Hive;
import com.davidmiguel.gobees.data.model.MeteoRecord;
import com.davidmiguel.gobees.data.model.Record;
import com.davidmiguel.gobees.data.model.Recording;
import com.davidmiguel.gobees.data.source.GoBeesDataSource;
import com.davidmiguel.gobees.logging.Log;
import com.davidmiguel.gobees.utils.DateTimeUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import io.realm.Realm;
import io.realm.RealmResults;

/**
 * Concrete implementation of a data source as a Realm db.
 */
public class GoBeesLocalDataSource implements GoBeesDataSource {

    // Fields names
    private static final String ID = "id";
    private static final String TIMESTAMP = "timestamp";
    private static final String LAST_REVISION = "lastRevision";

    private static GoBeesLocalDataSource instance;
    private Realm realm;


    private GoBeesLocalDataSource() {
        // Singleton
    }

    /**
     * Get GoBeesLocalDataSource instance.
     *
     * @return instance.
     */
    public static GoBeesLocalDataSource getInstance() {
        if (instance == null) {
            instance = new GoBeesLocalDataSource();
        }
        return instance;
    }

    @Override
    public void openDb() {
        realm = Realm.getDefaultInstance();
    }

    @Override
    public void closeDb() {
        realm.close();
    }

    @Override
    public void deleteAll(@NonNull TaskCallback callback) {
        try {
            realm.executeTransaction(new Realm.Transaction() {
                @Override
                public void execute(@NonNull Realm realm) {
                    realm.deleteAll();
                }
            });
            callback.onSuccess();
        } catch (Exception e) {
            Log.e(e, "Error: deleteAll()");
            callback.onFailure();
        }
    }

    @Override
    public void getApiaries(@NonNull GetApiariesCallback callback) {
        try {
            RealmResults<Apiary> apiaries = realm.where(Apiary.class).findAll();
            callback.onApiariesLoaded(realm.copyFromRealm(apiaries));
        } catch (Exception e) {
            Log.e(e, "Error: getApiaries()");
            callback.onDataNotAvailable();
        }
    }

    @Override
    public void getApiary(long apiaryId, @NonNull GetApiaryCallback callback) {
        try {
            Apiary apiary = realm.where(Apiary.class).equalTo(ID, apiaryId).findFirst();
            callback.onApiaryLoaded(realm.copyFromRealm(apiary));
        } catch (Exception e) {
            Log.e(e, "Error: getApiary()");
            callback.onDataNotAvailable();
        }
    }

    @Override
    public Apiary getApiaryBlocking(long apiaryId) {
        return realm.copyFromRealm(realm.where(Apiary.class).equalTo(ID, apiaryId).findFirst());
    }

    @Override
    public void saveApiary(@NonNull final Apiary apiary, @NonNull TaskCallback callback) {
        try {
            realm.executeTransaction(new Realm.Transaction() {
                @Override
                public void execute(@NonNull Realm realm) {
                    // Save apiary
                    realm.copyToRealmOrUpdate(apiary);
                }
            });
            callback.onSuccess();
        } catch (Exception e) {
            Log.e(e, "Error: saveApiary()");
            callback.onFailure();
        }
    }

    @Override
    public void refreshApiaries() {
        // Not required because the GoBeesRepository handles the logic of refreshing the
        // data from all the available data sources
    }

    @Override
    public void deleteApiary(long apiaryId, @NonNull TaskCallback callback) {
        try {
            // Get apiary
            final Apiary apiary = realm.where(Apiary.class).equalTo(ID, apiaryId).findFirst();
            // Delete
            realm.executeTransaction(new Realm.Transaction() {
                @Override
                public void execute(@NonNull Realm realm) {
                    if (apiary.getHives() != null) {
                        // Delete records of the hives
                        for (Hive hive : apiary.getHives()) {
                            if (hive.getRecords() != null) {
                                hive.getRecords().where().findAll().deleteAllFromRealm();
                            }
                        }
                        // Delete hives
                        apiary.getHives().where().findAll().deleteAllFromRealm();
                    }
                    // Delete current weather
                    if (apiary.getCurrentWeather() != null) {
                        apiary.getCurrentWeather().deleteFromRealm();
                    }
                    // Delete meteo records
                    if (apiary.getMeteoRecords() != null) {
                        apiary.getMeteoRecords().where().findAll().deleteAllFromRealm();
                    }
                    // Delete apiary
                    apiary.deleteFromRealm();
                }
            });
            callback.onSuccess();
        } catch (Exception e) {
            Log.e(e, "Error: deleteApiary()");
            callback.onFailure();
        }
    }

    @Override
    public void deleteAllApiaries(@NonNull TaskCallback callback) {
        try {
            final RealmResults<Apiary> apiaries = realm.where(Apiary.class).findAll();
            realm.executeTransaction(new Realm.Transaction() {
                @Override
                public void execute(@NonNull Realm realm) {
                    // Delete all apiaries
                    apiaries.deleteAllFromRealm();
                }
            });
            callback.onSuccess();
        } catch (Exception e) {
            Log.e(e, "Error: deleteAllApiaries()");
            callback.onFailure();
        }
    }

    @Override
    public void getNextApiaryId(@NonNull GetNextApiaryIdCallback callback) {
        Number nextId = realm.where(Apiary.class).max(ID);
        callback.onNextApiaryIdLoaded(nextId != null ? nextId.longValue() + 1 : 0);
    }

    @Override
    public Date getApiaryLastRevision(long apiaryId) {
        // Get apiary
        Apiary apiary = realm.where(Apiary.class).equalTo(ID, apiaryId).findFirst();
        // Get last revision date from all hives
        return apiary.getHives() == null
                ? null : apiary.getHives().where().maximumDate(LAST_REVISION);
    }

    @SuppressWarnings("ConstantConditions")
    @Override
    public void getHives(long apiaryId, @NonNull GetHivesCallback callback) {
        try {
            Apiary apiary = realm.where(Apiary.class).equalTo(ID, apiaryId).findFirst();
            callback.onHivesLoaded(realm.copyFromRealm(apiary.getHives()));
        } catch (Exception e) {
            Log.e(e, "Error: getHives()");
            callback.onDataNotAvailable();
        }
    }

    @Override
    public void getHive(long hiveId, @NonNull GetHiveCallback callback) {
        try {
            Hive hive = realm.where(Hive.class).equalTo(ID, hiveId).findFirst();
            callback.onHiveLoaded(realm.copyFromRealm(hive));
        } catch (Exception e) {
            Log.e(e, "Error: getHive()");
            callback.onDataNotAvailable();
        }
    }

    @Override
    public void getHiveWithRecordings(long hiveId, @NonNull GetHiveCallback callback) {
        try {
            // Get hive
            Hive hive = realm.where(Hive.class).equalTo(ID, hiveId).findFirst();
            if (hive == null || hive.getRecords() == null) {
                callback.onDataNotAvailable();
                return;
            }
            // Get records
            RealmResults<Record> records = hive.getRecords().where().findAll().sort(TIMESTAMP);
            // Classify records by date into recordings
            Date day;                   // Actual date of the recording
            Date nextDay = new Date(0); // Next day to the recording
            RealmResults<Record> filteredRecords;
            List<Recording> recordings = new ArrayList<>();
            while (true) {
                // Get all records greater than last recordings
                records = records.where().greaterThanOrEqualTo(TIMESTAMP, nextDay).findAll();
                if (records.isEmpty()) {
                    break;
                }
                // Get range of days to filter
                day = DateTimeUtils.getDateOnly(records.first().getTimestamp());
                nextDay = DateTimeUtils.getNextDay(day);
                // Filter records of that date and create recording
                filteredRecords = records.where()
                        .greaterThanOrEqualTo(TIMESTAMP, day)
                        .lessThan(TIMESTAMP, DateTimeUtils.getNextDay(nextDay))
                        .findAll();
                // Create recording
                recordings.add(new Recording(day, new ArrayList<>(filteredRecords)));
            }
            // Sort recordings (newest - oldest)
            Collections.reverse(recordings);
            // Set recordings to hive
            hive.setRecordings(recordings);
            // Return hive
            callback.onHiveLoaded(hive);
        } catch (Exception e) {
            Log.e(e, "Error: getHiveWithRecordings()");
            callback.onDataNotAvailable();
        }
    }

    @Override
    public void refreshHives(long apiaryId) {
        // Not required because the GoBeesRepository handles the logic of refreshing the
        // data from all the available data sources
    }

    @Override
    public void saveHive(final long apiaryId, @NonNull final Hive hive,
                         @NonNull TaskCallback callback) {
        try {
            realm.executeTransaction(new Realm.Transaction() {
                @Override
                public void execute(@NonNull Realm realm) {
                    // Save hive
                    realm.copyToRealmOrUpdate(hive);
                    // Add to apiary
                    Apiary apiary = realm.where(Apiary.class).equalTo(ID, apiaryId).findFirst();
                    if (apiary.getHives() != null) {
                        Hive h = apiary.getHives().where().equalTo(ID, hive.getId()).findFirst();
                        if (h == null) {
                            // New hive
                            apiary.addHive(hive);
                        }
                    }
                }
            });
            callback.onSuccess();
        } catch (Exception e) {
            Log.e(e, "Error: saveHive()");
            callback.onFailure();
        }
    }

    @Override
    public void deleteHive(long hiveId, @NonNull TaskCallback callback) {
        try {
            final Hive hive = realm.where(Hive.class).equalTo(ID, hiveId).findFirst();
            realm.executeTransaction(new Realm.Transaction() {
                @Override
                public void execute(@NonNull Realm realm) {
                    // Delete records of the hive
                    if (hive.getRecords() != null) {
                        hive.getRecords().where().findAll().deleteAllFromRealm();
                    }
                    // Delete hive
                    hive.deleteFromRealm();
                }
            });
            callback.onSuccess();
        } catch (Exception e) {
            Log.e(e, "Error: deleteHive()");
            callback.onFailure();
        }
    }

    @Override
    public void getNextHiveId(@NonNull GetNextHiveIdCallback callback) {
        Number nextId = realm.where(Hive.class).max(ID);
        callback.onNextHiveIdLoaded(nextId != null ? nextId.longValue() + 1 : 0);
    }

    @Override
    public void saveRecord(final long hiveId, @NonNull final Record record,
                           @NonNull TaskCallback callback) {
        try {
            realm.executeTransaction(new Realm.Transaction() {
                @Override
                public void execute(@NonNull Realm realm) {
                    // Save record
                    realm.copyToRealmOrUpdate(record);
                    // Add to hive
                    Hive hive = realm.where(Hive.class).equalTo(ID, hiveId).findFirst();
                    hive.addRecord(record);
                }
            });
            callback.onSuccess();
        } catch (Exception e) {
            Log.e(e, "Error: saveRecord()");
            callback.onFailure();
        }
    }

    @Override
    public void saveRecords(final long hiveId, @NonNull final List<Record> records,
                            @NonNull SaveRecordingCallback callback) {
        if (records.size() < 5) {
            // Recording too short
            callback.onRecordingTooShort();
            return;
        }
        try {
            // Get first id
            Number n = realm.where(Record.class).max(ID);
            long nextId = n != null ? n.longValue() + 1 : 0;
            // Set ids
            for (Record r : records) {
                r.setId(nextId++);
            }
            // Save records
            realm.executeTransaction(new Realm.Transaction() {
                @Override
                public void execute(@NonNull Realm realm) {
                    // Save records
                    for (Record r : records) {
                        realm.copyToRealmOrUpdate(r);
                    }
                    // Add to hive
                    Hive hive = realm.where(Hive.class).equalTo(ID, hiveId).findFirst();
                    hive.addRecords(records);
                    // Update last revision date (now)
                    hive.setLastRevision(new Date());
                }
            });
            callback.onSuccess();
        } catch (Exception e) {
            Log.e(e, "Error: saveRecords()");
            callback.onFailure();
        }
    }

    @Override
    public void getRecording(long apiaryId, long hiveId, Date start, Date end,
                             @NonNull GetRecordingCallback callback) {
        // Get apiary
        Apiary apiary = realm.where(Apiary.class).equalTo(ID, apiaryId).findFirst();
        if (apiary == null || apiary.getMeteoRecords() == null) {
            callback.onDataNotAvailable();
            return;
        }
        // Get hive
        Hive hive = realm.where(Hive.class).equalTo(ID, hiveId).findFirst();
        if (hive == null || hive.getRecords() == null) {
            callback.onDataNotAvailable();
            return;
        }
        // Get records
        RealmResults<Record> records = hive.getRecords()
                .where()
                .greaterThanOrEqualTo(TIMESTAMP, DateTimeUtils.setTime(start, 0, 0, 0, 0))
                .lessThanOrEqualTo(TIMESTAMP, DateTimeUtils.setTime(end, 23, 59, 59, 999))
                .findAll()
                .sort(TIMESTAMP);
        if (records.isEmpty()) {
            callback.onDataNotAvailable();
            return;
        }
        // Get weather data
        RealmResults<MeteoRecord> meteoRecords = apiary.getMeteoRecords()
                .where()
                .greaterThanOrEqualTo(TIMESTAMP, records.first().getTimestamp())
                .lessThanOrEqualTo(TIMESTAMP, records.last().getTimestamp())
                .findAll()
                .sort(TIMESTAMP);
        // Create recording
        Recording recording = new Recording(start,
                realm.copyFromRealm(records), realm.copyFromRealm(meteoRecords));
        callback.onRecordingLoaded(recording);
    }

    @Override
    public void deleteRecording(long hiveId, @NonNull Recording recording,
                                @NonNull TaskCallback callback) {
        try {
            // Get hive
            Hive hive = realm.where(Hive.class).equalTo(ID, hiveId).findFirst();
            if (hive == null) {
                callback.onFailure();
                return;
            }
            if (hive.getRecords() != null) {
                // Get records to delete
                final RealmResults<Record> records;
                records = hive.getRecords()
                        .where()
                        .greaterThanOrEqualTo(TIMESTAMP,
                                DateTimeUtils.setTime(recording.getDate(), 0, 0, 0, 0))
                        .lessThanOrEqualTo(TIMESTAMP,
                                DateTimeUtils.setTime(recording.getDate(), 23, 59, 59, 999))
                        .findAll();
                // Delete records
                realm.executeTransaction(new Realm.Transaction() {
                    @Override
                    public void execute(@NonNull Realm realm) {
                        records.deleteAllFromRealm();
                    }
                });
            }
            callback.onSuccess();
        } catch (Exception e) {
            Log.e(e, "Error: deleteRecording()");
            callback.onFailure();
        }
    }

    @SuppressWarnings("ConstantConditions")
    @Override
    public void updateApiariesCurrentWeather(final List<Apiary> apiariesToUpdate,
                                             @NonNull TaskCallback callback) {
        try {
            // Save meteo records
            realm.executeTransaction(new Realm.Transaction() {
                @Override
                public void execute(@NonNull Realm realm) {
                    // Get next id
                    Number n = realm.where(MeteoRecord.class).max(ID);
                    long nextId = n != null ? n.longValue() + 1 : 0;
                    // Save meteo records
                    for (Apiary apiary : apiariesToUpdate) {
                        MeteoRecord meteoRecord = apiary.getCurrentWeather();
                        meteoRecord.setId(nextId++);
                        realm.copyToRealmOrUpdate(meteoRecord);
                    }
                }
            });
            realm.executeTransaction(new Realm.Transaction() {
                @Override
                public void execute(@NonNull Realm realm) {
                    // Save apiaries with current weather updated
                    for (Apiary apiary : apiariesToUpdate) {
                        // Get apiary from db
                        Apiary requestedApiary = realm.where(Apiary.class)
                                .equalTo(ID, apiary.getId()).findFirst();
                        // Delete previous record
                        MeteoRecord oldMeteoRecord = requestedApiary.getCurrentWeather();
                        if (oldMeteoRecord != null) {
                            oldMeteoRecord.deleteFromRealm();
                        }
                        // Add new current weather to apiary
                        MeteoRecord meteoRecord = realm.where(MeteoRecord.class)
                                .equalTo(ID, apiary.getCurrentWeather().getId()).findFirst();
                        requestedApiary.setCurrentWeather(meteoRecord);
                    }
                }
            });
            callback.onSuccess();
        } catch (Exception e) {
            Log.e(e, "Error: updateApiariesCurrentWeather()");
            callback.onFailure();
        }
    }

    @Override
    public void getAndSaveMeteoRecord(@NonNull final Apiary apiary, @NonNull TaskCallback callback) {
        try {
            if (apiary.getMeteoRecords() == null || apiary.getMeteoRecords().size() != 1) {
                callback.onFailure();
            }
            final MeteoRecord meteoRecord = apiary.getMeteoRecords().first();
            realm.executeTransaction(new Realm.Transaction() {
                @Override
                public void execute(@NonNull Realm realm) {
                    // Get next id
                    Number n = realm.where(MeteoRecord.class).max(ID);
                    long nextId = n != null ? n.longValue() + 1 : 0;
                    // Save meteo records
                    meteoRecord.setId(nextId);
                    realm.copyToRealmOrUpdate(meteoRecord);
                    // Add meteo record to apiary
                    Apiary requestedApiary = realm.where(Apiary.class)
                            .equalTo(ID, apiary.getId()).findFirst();
                    requestedApiary.addMeteoRecord(meteoRecord);
                }
            });
            callback.onSuccess();
        } catch (Exception e) {
            Log.e(e, "Error: getAndSaveMeteoRecord()");
            callback.onFailure();
        }
    }

    @Override
    public void saveMeteoRecords(final long apiaryId, @NonNull final List<MeteoRecord> meteoRecords) {
        realm.executeTransaction(new Realm.Transaction() {
            @Override
            public void execute(@NonNull Realm realm) {
                // Get next id
                Number n = realm.where(MeteoRecord.class).max(ID);
                long nextId = n != null ? n.longValue() + 1 : 0;
                // Save meteo records
                for (MeteoRecord meteoRecord : meteoRecords) {
                    meteoRecord.setId(nextId++);
                    realm.copyToRealmOrUpdate(meteoRecord);
                }
                // Add meteo records to apiary
                Apiary requestedApiary = realm.where(Apiary.class)
                        .equalTo(ID, apiaryId).findFirst();
                requestedApiary.addMeteoRecords(meteoRecords);
            }
        });
    }

    @Override
    public void refreshRecordings(long hiveId) {
        // Not required because the GoBeesRepository handles the logic of refreshing the
        // data from all the available data sources
    }
}