/*
* Copyright 2016 Dmitry Monakhov.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* 12.02.16 9:10
*
*/
package monakhv.samlib.service;
import monakhv.samlib.data.AbstractSettings;
import monakhv.samlib.db.AuthorController;
import monakhv.samlib.db.entity.Author;
import monakhv.samlib.db.entity.Book;
import monakhv.samlib.db.entity.GroupBook;
import monakhv.samlib.db.entity.SamLibConfig;
import monakhv.samlib.exception.SamlibInterruptException;
import monakhv.samlib.exception.SamlibParseException;
import monakhv.samlib.http.HttpClientController;
import monakhv.samlib.log.Log;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
/**
* Data base Operations like Add or remove Authors
* Make book read or unread
* <p>
* Subject bus using java -rx
* Created by monakhv on 12.02.16.
*/
@SuppressWarnings("Convert2streamapi")
public class SamlibOperation {
private static final String DEBUG_TAG = "SamlibOperation";
public static final long SLEEP_INTERVAL_SECONDS = 1L;
public static final int SLEEP_DELAY_MIN = 5;
public static final int SLEEP_DELAY_MAX = 15;
protected final AuthorController mAuthorController;
private final AbstractSettings mSettingsHelper;
private final HttpClientController mHttpClientController;
private final GuiEventBus mGuiEventBus;
private Thread mThread;
public SamlibOperation(AuthorController sql, AbstractSettings settingsHelper, HttpClientController httpClientController, GuiEventBus guiEventBus) {
mAuthorController = sql;
mSettingsHelper = settingsHelper;
mHttpClientController = httpClientController;
mGuiEventBus = guiEventBus;
}
public void makeBookReadFlip(final Book book, final BookGuiState bState, final AuthorGuiState aState) {
mThread = new Thread() {
@Override
public void run() {
super.run();
runBookReadFlip(book, bState, aState);
}
};
mThread.start();
}
public void makeGroupReadFlip(final GroupBook groupBook, final BookGuiState bState, final AuthorGuiState aState) {
mThread = new Thread() {
@Override
public void run() {
super.run();
runGroupReadFlip(groupBook, bState, aState);
}
};
mThread.start();
}
public void makeAuthorRead(final Author author, final AuthorGuiState state) {
mThread = new Thread() {
@Override
public void run() {
super.run();
runAuthorRead(author, state);
}
};
mThread.start();
}
public void makeAuthorDel(final Author author, final AuthorGuiState state) {
mThread = new Thread() {
@Override
public void run() {
super.run();
runAuthorDel(author, state);
}
};
mThread.start();
}
public void makeAuthorAdd(final ArrayList<String> urls, final AuthorGuiState state) {
mThread = new Thread() {
@Override
public void run() {
super.run();
runAuthorAdd(urls, state);
}
};
mThread.start();
}
public void makeUpdateTags(final AuthorGuiState state) {
mThread = new Thread() {
@Override
public void run() {
super.run();
runUpdateTags(state);
}
};
mThread.start();
}
public void makeGroupReload(final GroupBook groupBook, final BookGuiState state) {
mThread = new Thread() {
@Override
public void run() {
super.run();
runGroupReload(groupBook, state);
}
};
mThread.start();
}
public void makeBookReload(final Book book, final BookGuiState state) {
mThread = new Thread() {
@Override
public void run() {
super.run();
runBookReload(book, state);
}
};
mThread.start();
}
public void makeAuthorReload(final Author author, final AuthorGuiState state) {
mThread = new Thread() {
@Override
public void run() {
super.run();
runAuthorReload(author, state);
}
};
mThread.start();
}
private void runAuthorReload(Author author, AuthorGuiState state) {
Author a = mAuthorController.getById(author.getId());
int idx = getAuthorIndex(a, state);
makeGuiUpdate(new GuiUpdateObject(a, idx));
}
/**
* Special method to make Author read, also make sure that all book re read either
*
* @param a Author
* @return true if success
*/
private boolean runAuthorRead(Author a, AuthorGuiState state) {
if (a == null) {
Log.e(DEBUG_TAG, "Author not found to update");
return false;
}
if (!a.isIsNew()) {
Log.d(DEBUG_TAG, "Author is read - no update need");
return false;
}
List<GroupBook> newGroups = mAuthorController.getGroupBookController().getByAuthorNew(a);
int i = mAuthorController.markRead(a);
int idx = getAuthorIndex(a, state);
makeGuiUpdate(new GuiUpdateObject(a, idx));
List<GroupBook> groupBooks = mAuthorController.getGroupBookController().getByAuthor(a);
Log.d(DEBUG_TAG, "Update author status: " + i + " sort " + idx + " groups: " + groupBooks.size());
for (GroupBook groupBook : newGroups) {
//groupBook.setBooks(mAuthorController.getBookController().getBookForGroup(groupBook,b));
makeGuiUpdate(new GuiUpdateObject(groupBook, groupBooks.indexOf(groupBook)));
}
if (groupBooks.size() == 0) {//special case when Author has no groups
makeGuiUpdate(new GuiUpdateObject(GuiUpdateObject.ObjectType.GROUP, null));
}
return true;
}
@SuppressWarnings("ConstantConditions")
private void runGroupReadFlip(GroupBook groupBook, BookGuiState bState, AuthorGuiState aState) {
List<Book> books=groupBook.getBooks();
if (books == null){
mAuthorController.getBookController().getBookForGroup(groupBook, bState.mSortOrder);
books=groupBook.getBooks();
}
Log.d(DEBUG_TAG,"runGroupReadFlip: id "+groupBook.getId()+" book number: "+books.size());
for (Book book : books) {
if (book.isIsNew()) {
Log.d(DEBUG_TAG,"runGroupReadFlip: clean new mark for the book: "+book.getTitle());
mAuthorController.getBookController().markRead(book);
}
}
Author author = groupBook.getAuthor();
if (mAuthorController.testMarkRead(author)) {
if (aState != null) {
makeGuiUpdate(new GuiUpdateObject(author, getAuthorIndex(author, aState)));
}
}
if (groupBook.getId() <0) {
if (groupBook.getId() ==-2){//selected books
GroupBook g = mAuthorController.getBookController().getSelectedGroup(bState.mSortOrder);
makeGuiUpdate(new GuiUpdateObject(g, 0));
}else if (groupBook.getId() ==-1){
mAuthorController.getBookController().getBookForGroup(groupBook, bState.mSortOrder);
makeGuiUpdate(new GuiUpdateObject(groupBook, 0));
}
} else {
List<GroupBook> rr = mAuthorController.getGroupBookController().getByAuthor(groupBook.getAuthor());
GroupBook g = mAuthorController.getGroupBookController().getById(groupBook.getId());
mAuthorController.getBookController().getBookForGroup(g, bState.mSortOrder);
makeGuiUpdate(new GuiUpdateObject(g, rr.indexOf(groupBook)));
}
}
/**
* Invert read book flag
* Adjust author flag either
*
* @param book Book
*/
private void runBookReadFlip(Book book, BookGuiState bState, AuthorGuiState aState) {
if (book == null) {
Log.e(DEBUG_TAG, "makeBookReadFlip: book is null ");
return;
}
Log.d(DEBUG_TAG, "makeBookReadFlip: book_id = " + book.getId() + " author_id = " + book.getAuthor().getId());
Author a;
if (book.isIsNew()) {
mAuthorController.getBookController().markRead(book);
a = mAuthorController.getById(book.getAuthor().getId());
if (mAuthorController.testMarkRead(a) && aState != null) {
makeGuiUpdate(new GuiUpdateObject(a, getAuthorIndex(a, aState)));
}
} else {
mAuthorController.getBookController().markUnRead(book);
a = mAuthorController.getById(book.getAuthor().getId());
if (mAuthorController.testMarkRead(a) && aState != null) {
makeGuiUpdate(new GuiUpdateObject(a, getAuthorIndex(a, aState)));
}
}
makeGuiUpdate(new GuiUpdateObject(book, loadBooks(book, bState).indexOf(book)));
}
private void runGroupReload(GroupBook groupBook, BookGuiState bState) {
Author author = mAuthorController.getById(bState.mAuthorId);
if (groupBook == null) {//Author has no group that means all book are in the single default group
List<Book> books = mAuthorController.getBookController().getAll(author, bState.mSortOrder);
makeGuiUpdate(new GuiUpdateObject(GuiUpdateObject.ObjectType.GROUP, books));
} else {
GroupBook g = mAuthorController.getGroupBookController().getById(groupBook.getId());
mAuthorController.getBookController().getBookForGroup(g, bState.mSortOrder);
List<GroupBook> gg = mAuthorController.getGroupBookController().getByAuthor(author);
makeGuiUpdate(new GuiUpdateObject(g, gg.indexOf(g)));
}
}
private void runBookReload(Book book, BookGuiState state) {
List<Book> books = loadBooks(book, state);
makeGuiUpdate(new GuiUpdateObject(book, books.indexOf(book)));
}
/**
* Delete author from DB
*
* @param author Author
*/
private void runAuthorDel(Author author, AuthorGuiState state) {
Result result = new Result(true);
int id = author.getId();
int idx = getAuthorIndex(author, state);
int res = mAuthorController.delete(author);
Log.d(DEBUG_TAG, "makeAuthorDel: Author id " + id + " deleted, status " + res);
if (res == 1) {
++result.numberOfDeleted;
makeGuiUpdate(new GuiUpdateObject(author, idx, GuiUpdateObject.UpdateType.DELETE));
makeGuiUpdate(new GuiUpdateObject(result, GuiUpdateObject.UpdateType.DELETE));
return;
}
result.mRes = false;
makeGuiUpdate(new GuiUpdateObject(result, GuiUpdateObject.UpdateType.DELETE));
}
/**
* Add authors
*
* @param urls list of author urls
*/
void runAuthorAdd(ArrayList<String> urls, AuthorGuiState state) {
Result result = new Result(true);
Random rnd = new Random(Calendar.getInstance().getTimeInMillis());
result.totalToAdd = urls.size();
for (String url : urls) {
Author a = loadAuthor(result, url);
if (a != null) {
long author_id = mAuthorController.insert(a);
++result.numberOfAdded;
int idx = getAuthorIndex((int) author_id, state);
makeGuiUpdate(new GuiUpdateObject(mAuthorController.getById(author_id), idx, GuiUpdateObject.UpdateType.ADD));
}
long sleep;
if (mSettingsHelper.isUpdateDelay()) {
sleep = rnd.nextInt(SLEEP_DELAY_MAX - SLEEP_DELAY_MIN + 1) + SLEEP_DELAY_MIN;
} else {
sleep = SLEEP_INTERVAL_SECONDS;
}
try {
Log.d(DEBUG_TAG, "makeAuthorAdd: sleep " + sleep + " seconds");
TimeUnit.SECONDS.sleep(sleep);
} catch (InterruptedException e) {
Log.i(DEBUG_TAG, "makeAuthorAdd: Sleep interrupted exiting", e);
result.mRes = false;
makeGuiUpdate(new GuiUpdateObject(result, GuiUpdateObject.UpdateType.ADD));
return;
}
}
result.mRes = true;
makeGuiUpdate(new GuiUpdateObject(result, GuiUpdateObject.UpdateType.ADD));
}
/**
* Recalculate allTagsString for all Authors
*/
private void runUpdateTags(AuthorGuiState state) {
for (Author author : mAuthorController.getAll()) {
String allTagString = mAuthorController.getAllTagString(author);
if (!author.getAll_tags_name().equals(allTagString)) {
author.setAll_tags_name(allTagString);
if (state != null) {
makeGuiUpdate(new GuiUpdateObject(author, getAuthorIndex(author, state)));
}
}
}
makeGuiUpdate(new GuiUpdateObject(GuiUpdateObject.ObjectType.TAG, null));
}
private void makeGuiUpdate(GuiUpdateObject guiUpdateObject) {
mGuiEventBus.post(guiUpdateObject);
}
private int getAuthorIndex(Author author, AuthorGuiState state) {
final List<Author> authors = mAuthorController.getAll(state.mSelectedTagId, state.mSorOrder);
return authors.indexOf(author);
}
private int getAuthorIndex(int id, AuthorGuiState state) {
Author author = mAuthorController.getById(id);
final List<Author> authors = mAuthorController.getAll(state.mSelectedTagId, state.mSorOrder);
return authors.indexOf(author);
}
/**
* Return list of books of group for given book
*
* @param book the Book to determine the Group
* @param bState Book Gui state
* @return List of book
*/
private List<Book> loadBooks(Book book, BookGuiState bState) {
GroupBook groupBook;
List<Book> books;
if (bState.mAuthorId == SamLibConfig.SELECTED_BOOK_ID) {//Selected book search
groupBook = mAuthorController.getBookController().getSelectedGroup(bState.mSortOrder);
} else {
groupBook = mAuthorController.getGroupBookController().getByBook(book);
Log.d(DEBUG_TAG,"groupBook id: "+groupBook.getId());
mAuthorController.getBookController().getBookForGroup(groupBook, bState.mSortOrder);
}
books = groupBook.getBooks();
book.setGroupBook(groupBook);
return books;
}
private Author loadAuthor(Result res, String url) {
Author a;
String text;
text = testURL(url);
if (text == null) {
Log.e(DEBUG_TAG, "loadAuthor: URL syntax error: " + url);
return null;
}
Author ta = mAuthorController.getByUrl(text);
if (ta != null) {
Log.i(DEBUG_TAG, "loadAuthor: Ignore Double entries: " + text);
++res.doubleAdd;
return null;
}
try {
a = mHttpClientController.addAuthor(text, new Author());
} catch (IOException ex) {
Log.e(DEBUG_TAG, "loadAuthor: DownLoad Error for URL: " + text, ex);
return null;
} catch (SamlibParseException ex) {
Log.e(DEBUG_TAG, "loadAuthor: Author parsing Error: " + text, ex);
return null;
} catch (IllegalArgumentException ex) {
Log.e(DEBUG_TAG, "loadAuthor: URL Parsing exception: " + text, ex);
return null;
} catch (SamlibInterruptException e) {
Log.e(DEBUG_TAG, "loadAuthor: Interrupted catch: " + text, e);
return null;
}
return a;
}
/**
* URL syntax checkout
*
* @param url original URL
* @return reduced URL without host prefix or NULL if the syntax is wrong
*/
private String testURL(String url) {
Log.d(DEBUG_TAG, "testURL: Got text: " + url);
return SamLibConfig.reduceUrl(url);
}
}