/**
* Copyright (C) 2016 eBusiness Information
*
* This file is part of OSM Contributor.
*
* OSM Contributor 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.
*
* OSM Contributor 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 OSM Contributor. If not, see <http://www.gnu.org/licenses/>.
*/
package io.jawg.osmcontributor.ui.managers;
import android.app.Application;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import javax.inject.Inject;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import io.jawg.osmcontributor.utils.ConfigManager;
import io.jawg.osmcontributor.database.helper.DatabaseHelper;
import io.jawg.osmcontributor.database.dao.CommentDao;
import io.jawg.osmcontributor.database.dao.NoteDao;
import io.jawg.osmcontributor.model.events.NoteLoadedEvent;
import io.jawg.osmcontributor.model.events.NoteSavedEvent;
import io.jawg.osmcontributor.model.events.NotesArpiLoadedEvent;
import io.jawg.osmcontributor.model.events.NotesLoadedEvent;
import io.jawg.osmcontributor.model.events.PleaseLoadNoteEvent;
import io.jawg.osmcontributor.model.events.PleaseLoadNoteForArpiEvent;
import io.jawg.osmcontributor.model.events.PleaseLoadNotesEvent;
import io.jawg.osmcontributor.model.events.ResetDatabaseEvent;
import io.jawg.osmcontributor.model.entities.Comment;
import io.jawg.osmcontributor.model.entities.Note;
import io.jawg.osmcontributor.ui.activities.NoteActivity;
import io.jawg.osmcontributor.ui.events.map.NewNoteCreatedEvent;
import io.jawg.osmcontributor.ui.events.map.PleaseApplyNewComment;
import io.jawg.osmcontributor.ui.events.note.ApplyNewCommentFailedEvent;
import io.jawg.osmcontributor.rest.managers.SyncNoteManager;
import io.jawg.osmcontributor.rest.events.SyncFinishUploadNote;
import io.jawg.osmcontributor.utils.Box;
import timber.log.Timber;
import static io.jawg.osmcontributor.database.helper.DatabaseHelper.loadLazyForeignCollection;
/**
* Manager class for Notes and Comments.
* Provides a number of methods to manipulate the {@link io.jawg.osmcontributor.model.entities.Note}
* and {@link io.jawg.osmcontributor.model.entities.Comment} in the database that should be used instead
* of calling the {@link io.jawg.osmcontributor.database.dao.NoteDao}
* and {@link io.jawg.osmcontributor.database.dao.NoteDao}.
*/
public class NoteManager {
CommentDao commentDao;
NoteDao noteDao;
DatabaseHelper databaseHelper;
ConfigManager configManager;
EventBus bus;
Application application;
SyncNoteManager syncNoteManager;
LoginManager loginManager;
@Inject
public NoteManager(NoteDao noteDao, CommentDao commentDao, DatabaseHelper databaseHelper, ConfigManager configManager, EventBus bus, Application application, SyncNoteManager syncNoteManager, LoginManager loginManager) {
this.noteDao = noteDao;
this.commentDao = commentDao;
this.databaseHelper = databaseHelper;
this.configManager = configManager;
this.application = application;
this.bus = bus;
this.syncNoteManager = syncNoteManager;
this.loginManager = loginManager;
}
// ********************************
// ************ Events ************
// ********************************
@Subscribe(threadMode = ThreadMode.ASYNC)
public void onPleaseLoadNoteEvent(PleaseLoadNoteEvent event) {
bus.post(new NoteLoadedEvent(queryForId(event.getNoteId())));
}
@Subscribe(threadMode = ThreadMode.ASYNC)
public void onPleaseLoadNotesEvent(PleaseLoadNotesEvent event) {
loadNotes(event);
}
@Subscribe(threadMode = ThreadMode.ASYNC)
public void onPleaseApplyNewComment(PleaseApplyNewComment event) {
Timber.d("please apply new comment");
if (loginManager.checkCredentials()) {
Note note = syncNoteManager.remoteAddComment(createComment(event.getNote(), event.getAction(), event.getText()));
if (note != null) {
mergeBackendNote(note);
bus.post(new NewNoteCreatedEvent(note.getId()));
bus.post(new SyncFinishUploadNote(note));
}
} else {
bus.post(new ApplyNewCommentFailedEvent());
}
}
@Subscribe(threadMode = ThreadMode.ASYNC)
public void onResetDatabaseEvent(ResetDatabaseEvent event) {
resetDatabase();
}
@Subscribe(threadMode = ThreadMode.ASYNC)
public void onPleaseLoadNoteForArpiEvent(PleaseLoadNoteForArpiEvent event) {
List<Note> notes = noteDao.queryForAllInRect(event.getBox());
bus.post(new NotesArpiLoadedEvent(notes));
}
// ********************************
// ************ public ************
// ********************************
/**
* Method saving a list Notes and the associated foreign collections (comments).
* <p/>
* Do not call the DAO directly to save a List of Notes, use this method.
*
* @param notes The Notes to save.
* @return The saved Notes.
*/
public List<Note> saveNotes(final List<Note> notes) {
return databaseHelper.callInTransaction(new Callable<List<Note>>() {
@Override
public List<Note> call() throws Exception {
List<Note> result = new ArrayList<>(notes.size());
for (Note note : notes) {
result.add(saveNoteNoTransactionMgmt(note));
}
return result;
}
});
}
/**
* Method saving a note and the associated foreign collection (comments) without transaction management.
* <p/>
* Do not call the DAO directly to save a note, use this method.
*
* @param note The note to save.
* @return The saved note.
* @see #saveNote(Note)
*/
private Note saveNoteNoTransactionMgmt(Note note) {
commentDao.deleteByNoteIdAndUpdated(note.getId(), false);
noteDao.createOrUpdate(note);
if (note.getComments() != null) {
for (Comment comment : note.getComments()) {
commentDao.createOrUpdate(comment);
}
}
bus.post(new NoteSavedEvent(note));
return note;
}
/**
* Method saving a note and the associated foreign collection (comments).
* <p/>
* Do not call the DAO directly to save a note, use this method.
*
* @param note The note to save.
* @return The saved note.
*/
public Note saveNote(final Note note) {
return databaseHelper.callInTransaction(new Callable<Note>() {
@Override
public Note call() throws Exception {
return saveNoteNoTransactionMgmt(note);
}
});
}
/**
* Query for a Note with a given id eagerly.
*
* @param id The id of the Note to load.
* @return The queried Note.
*/
public Note queryForId(Long id) {
Note note = noteDao.queryForId(id);
if (note == null) {
return null;
}
note.setComments(loadLazyForeignCollection(note.getComments()));
return note;
}
/**
* Merge Note in parameters to the already in the database.
*
* @param remoteNote The Note to merge.
*/
public void mergeBackendNote(Note remoteNote) {
Note localNote = noteDao.queryByBackendId(remoteNote.getBackendId());
if (localNote != null) {
remoteNote.setId(localNote.getId());
}
saveNote(remoteNote);
}
/**
* Merge Notes in parameters to those already in the database.
*
* @param remoteNotes The Notes to merge.
*/
public void mergeBackendNotes(List<Note> remoteNotes) {
List<Note> toMergeNotes = new ArrayList<>();
Map<String, Note> remoteNotesMap = new HashMap<>();
// Map remote Note backend Ids
for (Note note : remoteNotes) {
remoteNotesMap.put(note.getBackendId(), note);
}
// List matching Notes
List<Note> localNotes = noteDao.queryByBackendIds(remoteNotesMap.keySet());
Map<String, Note> localNotesMap = new HashMap<>();
// Map matching local Notes
for (Note localNote : localNotes) {
localNotesMap.put(localNote.getBackendId(), localNote);
}
// Browse remote notes
for (Note remoteNote : remoteNotes) {
Note localNote = localNotesMap.get(remoteNote.getBackendId());
if (localNote != null) {
remoteNote.setId(localNote.getId());
}
// This Note should be updated
toMergeNotes.add(remoteNote);
}
// saveNotes of either new or existing Notes
saveNotes(toMergeNotes);
}
/**
* Query for all the Notes contained in the bounds defined by the box.
*
* @param box Bounds of the search in latitude and longitude coordinates.
* @return The Notes contained in the box.
*/
public List<Note> queryForAllInRect(Box box) {
return noteDao.queryForAllInRect(box);
}
/**
* Create a Comment with the given parameters without modifying the Note.
*
* @param note The Note associated with the comment.
* @param action The action of the comment.
* @param comment The comment of the comment.
* @return The created comment.
*/
public Comment createComment(Note note, String action, String comment) {
Comment newComment = new Comment();
newComment.setText(comment);
switch (action) {
case NoteActivity.CLOSE:
newComment.setAction(Comment.ACTION_CLOSE);
break;
case NoteActivity.COMMENT:
newComment.setAction(Comment.ACTION_COMMENT);
break;
case NoteActivity.REOPEN:
newComment.setAction(Comment.ACTION_REOPEN);
break;
default:
newComment.setAction(Comment.ACTION_OPEN);
break;
}
newComment.setNote(note);
return newComment;
}
/**
* Reset the database : delete all the Notes and Comments of the database.
*
* @return Whether the reset was successful.
*/
public Boolean resetDatabase() {
return databaseHelper.callInTransaction(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
noteDao.deleteAll();
commentDao.deleteAll();
return true;
}
});
}
// *********************************
// ************ private ************
// *********************************
/**
* Send a {@link NotesLoadedEvent} containing all the Notes
* in the Box of the {@link PleaseLoadNotesEvent}.
*
* @param event Event containing the box to load.
*/
private void loadNotes(PleaseLoadNotesEvent event) {
bus.post(new NotesLoadedEvent(event.getBox(), queryForAllInRect(event.getBox())));
}
}