package com.jparkie.aizoban.controllers.sources;
import android.database.sqlite.SQLiteDatabase;
import com.jparkie.aizoban.controllers.caches.CacheProvider;
import com.jparkie.aizoban.controllers.databases.ApplicationContract;
import com.jparkie.aizoban.controllers.databases.ApplicationSQLiteOpenHelper;
import com.jparkie.aizoban.controllers.databases.LibraryContract;
import com.jparkie.aizoban.controllers.databases.LibrarySQLiteOpenHelper;
import com.jparkie.aizoban.controllers.factories.DefaultFactory;
import com.jparkie.aizoban.controllers.networks.MangaService;
import com.jparkie.aizoban.models.Chapter;
import com.jparkie.aizoban.models.Manga;
import com.jparkie.aizoban.utils.wrappers.RequestWrapper;
import com.squareup.okhttp.Response;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import rx.Observable;
import rx.Subscriber;
import rx.functions.Action1;
import rx.functions.Func1;
import static nl.qbusict.cupboard.CupboardFactory.cupboard;
public class Italian_MangaEden implements Source {
public static final String NAME = "MangaEden (IT)";
public static final String BASE_URL = "www.mangaeden.com";
private static final String INITIAL_UPDATE_URL = "http://www.mangaeden.com/ajax/news/1/1";
@Override
public Observable<String> getName() {
return Observable.just(NAME);
}
@Override
public Observable<String> getBaseUrl() {
return Observable.just(BASE_URL);
}
@Override
public Observable<String> getInitialUpdateUrl() {
return Observable.just(INITIAL_UPDATE_URL);
}
@Override
public Observable<List<String>> getGenres() {
List<String> genres = new ArrayList<String>();
return Observable.just(genres);
}
@Override
public Observable<UpdatePageMarker> pullLatestUpdatesFromNetwork(final UpdatePageMarker newUpdate) {
return MangaService.getPermanentInstance()
.getResponse(newUpdate.getNextPageUrl())
.flatMap(new Func1<Response, Observable<String>>() {
@Override
public Observable<String> call(Response response) {
return MangaService.mapResponseToString(response);
}
})
.flatMap(new Func1<String, Observable<UpdatePageMarker>>() {
@Override
public Observable<UpdatePageMarker> call(String unparsedHtml) {
return Observable.just(parseHtmlToLatestUpdates(newUpdate, unparsedHtml));
}
});
}
private UpdatePageMarker parseHtmlToLatestUpdates(UpdatePageMarker newUpdate, String unparsedHtml) {
Document parsedDocument = Jsoup.parse(unparsedHtml);
List<Manga> updatedMangaList = scrapeUpdateMangasFromParsedDocument(parsedDocument);
updateLibraryInDatabase(updatedMangaList);
String nextPageUrl = findNextUrlFromUpdatePageMarker(newUpdate);
int lastMangaPostion = updatedMangaList.size();
return new UpdatePageMarker(nextPageUrl, lastMangaPostion);
}
private List<Manga> scrapeUpdateMangasFromParsedDocument(Document parsedDocument) {
List<Manga> updatedMangaList = new ArrayList<Manga>();
Elements updatedHtmlBlocks = parsedDocument.select("body > li");
for (Element currentHtmlBlock : updatedHtmlBlocks) {
Manga currentlyUpdatedManga = constructMangaFromHtmlBlock(currentHtmlBlock);
updatedMangaList.add(currentlyUpdatedManga);
}
return updatedMangaList;
}
private Manga constructMangaFromHtmlBlock(Element htmlBlock) {
Manga mangaFromHtmlBlock = DefaultFactory.Manga.constructDefault();
mangaFromHtmlBlock.setSource(NAME);
Element urlElement = htmlBlock.select("div.newsManga").first();
Element nameElement = htmlBlock.select("div.manga_tooltop_header > a").first();
Element updateElement = htmlBlock.select("div.chapterDate").first();
if (urlElement != null) {
String fieldUrl = "https://www.mangaeden.com/api/manga/" + urlElement.id().substring(0, 24) + "/";
mangaFromHtmlBlock.setUrl(fieldUrl);
}
if (nameElement != null) {
String fieldName = nameElement.text();
mangaFromHtmlBlock.setName(fieldName);
}
if (updateElement != null) {
long fieldUpdate = parseUpdateFromElement(updateElement);
mangaFromHtmlBlock.setUpdated(fieldUpdate);
}
int updateCount = htmlBlock.select("div.chapterDate").size();
mangaFromHtmlBlock.setUpdateCount(updateCount);
return mangaFromHtmlBlock;
}
private long parseUpdateFromElement(Element updateElement) {
String updatedDateAsString = updateElement.text();
if (updatedDateAsString.contains("Today")) {
Calendar today = Calendar.getInstance();
today.set(Calendar.HOUR_OF_DAY, 0);
today.set(Calendar.MINUTE, 0);
today.set(Calendar.SECOND, 0);
today.set(Calendar.MILLISECOND, 0);
try {
Date withoutDay = new SimpleDateFormat("h:mm a", Locale.ENGLISH).parse(updatedDateAsString.replace("Today", ""));
return today.getTimeInMillis() + withoutDay.getTime();
} catch (ParseException e) {
return today.getTimeInMillis();
}
} else if (updatedDateAsString.contains("Yesterday")) {
Calendar yesterday = Calendar.getInstance();
yesterday.add(Calendar.DATE, -1);
yesterday.set(Calendar.HOUR_OF_DAY, 0);
yesterday.set(Calendar.MINUTE, 0);
yesterday.set(Calendar.SECOND, 0);
yesterday.set(Calendar.MILLISECOND, 0);
try {
Date withoutDay = new SimpleDateFormat("h:mm a", Locale.ENGLISH).parse(updatedDateAsString.replace("Yesterday", ""));
return yesterday.getTimeInMillis() + withoutDay.getTime();
} catch (ParseException e) {
return yesterday.getTimeInMillis();
}
} else {
try {
Date specificDate = new SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH).parse(updatedDateAsString);
return specificDate.getTime();
} catch (ParseException e) {
// Do Nothing.
}
}
return DefaultFactory.Manga.DEFAULT_UPDATED;
}
private void updateLibraryInDatabase(List<Manga> mangaList) {
LibrarySQLiteOpenHelper librarySQLiteOpenHelper = LibrarySQLiteOpenHelper.getInstance();
SQLiteDatabase sqLiteDatabase = librarySQLiteOpenHelper.getWritableDatabase();
sqLiteDatabase.beginTransaction();
try {
for (Manga currentManga : mangaList) {
StringBuilder selection = new StringBuilder();
List<String> selectionArgs = new ArrayList<String>();
selection.append(LibraryContract.Manga.COLUMN_SOURCE + " = ?");
selectionArgs.add(NAME);
selection.append(" AND ").append(LibraryContract.Manga.COLUMN_URL + " = ?");
selectionArgs.add(currentManga.getUrl());
Manga existingManga = cupboard().withDatabase(sqLiteDatabase).query(Manga.class)
.withSelection(selection.toString(), selectionArgs.toArray(new String[selectionArgs.size()]))
.limit(1)
.get();
if (existingManga != null) {
existingManga.setUpdated(currentManga.getUpdated());
existingManga.setUpdateCount(currentManga.getUpdateCount());
cupboard().withDatabase(sqLiteDatabase).put(existingManga);
}
}
sqLiteDatabase.setTransactionSuccessful();
} finally {
sqLiteDatabase.endTransaction();
}
}
private String findNextUrlFromUpdatePageMarker(UpdatePageMarker newUpdate) {
String requestUrl = newUpdate.getNextPageUrl();
if (!requestUrl.equals(DefaultFactory.UpdatePageMarker.DEFAULT_NEXT_PAGE_URL)) {
String currentPageNumber = requestUrl.substring(0, requestUrl.lastIndexOf("/1")).replaceAll("[^\\d]", "");
int newPageNumber = Integer.parseInt(currentPageNumber) + 1;
return "http://www.mangaeden.com/ajax/news/" + newPageNumber + "/1";
}
return DefaultFactory.UpdatePageMarker.DEFAULT_NEXT_PAGE_URL;
}
@Override
public Observable<Manga> pullMangaFromNetwork(final RequestWrapper request) {
return MangaService.getPermanentInstance()
.getResponse(request.getUrl())
.flatMap(new Func1<Response, Observable<String>>() {
@Override
public Observable<String> call(Response response) {
return MangaService.mapResponseToString(response);
}
})
.flatMap(new Func1<String, Observable<Manga>>() {
@Override
public Observable<Manga> call(final String unparsedJson) {
return Observable.create(new Observable.OnSubscribe<Manga>() {
@Override
public void call(Subscriber<? super Manga> subscriber) {
try {
subscriber.onNext(parseJsonToManga(request, unparsedJson));
subscriber.onCompleted();
} catch (Throwable e) {
subscriber.onError(e);
}
}
});
}
});
}
private Manga parseJsonToManga(RequestWrapper request, String unparsedJson) throws JSONException {
JSONObject parsedJsonObject = new JSONObject(unparsedJson);
String fieldGenre = "";
JSONArray genreArrayNodes = parsedJsonObject.getJSONArray("categories");
for (int index = 0; index < genreArrayNodes.length(); index++) {
if (index != genreArrayNodes.length() - 1) {
fieldGenre += genreArrayNodes.getString(index) + ", ";
} else {
fieldGenre += genreArrayNodes.getString(index);
}
}
LibrarySQLiteOpenHelper librarySQLiteOpenHelper = LibrarySQLiteOpenHelper.getInstance();
SQLiteDatabase sqLiteDatabase = librarySQLiteOpenHelper.getWritableDatabase();
StringBuilder selection = new StringBuilder();
List<String> selectionArgs = new ArrayList<String>();
selection.append(LibraryContract.Manga.COLUMN_SOURCE + " = ?");
selectionArgs.add(NAME);
selection.append(" AND ").append(LibraryContract.Manga.COLUMN_URL + " = ?");
selectionArgs.add(request.getUrl());
Manga newManga = cupboard().withDatabase(sqLiteDatabase).query(Manga.class)
.withSelection(selection.toString(), selectionArgs.toArray(new String[selectionArgs.size()]))
.limit(1)
.get();
newManga.setArtist(parsedJsonObject.getString("artist"));
newManga.setAuthor(parsedJsonObject.getString("author"));
newManga.setDescription(parsedJsonObject.getString("description").trim());
newManga.setGenre(fieldGenre);
newManga.setCompleted(parsedJsonObject.getInt("status") == 2);
newManga.setThumbnailUrl("https://cdn.mangaeden.com/mangasimg/" + parsedJsonObject.getString("image"));
newManga.setInitialized(true);
cupboard().withDatabase(sqLiteDatabase).put(newManga);
return newManga;
}
@Override
public Observable<List<Chapter>> pullChaptersFromNetwork(final RequestWrapper request) {
return MangaService.getPermanentInstance()
.getResponse(request.getUrl())
.flatMap(new Func1<Response, Observable<String>>() {
@Override
public Observable<String> call(Response response) {
return MangaService.mapResponseToString(response);
}
})
.flatMap(new Func1<String, Observable<List<Chapter>>>() {
@Override
public Observable<List<Chapter>> call(final String unparsedJson) {
return Observable.create(new Observable.OnSubscribe<List<Chapter>>() {
@Override
public void call(Subscriber<? super List<Chapter>> subscriber) {
try {
subscriber.onNext(parseJsonToChapters(request, unparsedJson));
subscriber.onCompleted();
} catch (Throwable e) {
subscriber.onError(e);
}
}
});
}
});
}
private List<Chapter> parseJsonToChapters(RequestWrapper request, String unparsedJson) throws JSONException{
JSONObject parsedJsonObject = new JSONObject(unparsedJson);
List<Chapter> chapterList = scrapeChaptersFromParsedJson(parsedJsonObject);
chapterList = setSourceForChapterList(chapterList);
chapterList = setParentUrlForChapterList(chapterList, request.getUrl());
chapterList = setNumberForChapterList(chapterList);
saveChaptersToDatabase(chapterList, request.getUrl());
return chapterList;
}
private List<Chapter> scrapeChaptersFromParsedJson(JSONObject parsedJsonObject) throws JSONException {
List<Chapter> chapterList = new ArrayList<Chapter>();
String mangaName = parsedJsonObject.getString("title");
JSONArray chapterArrayNodes = parsedJsonObject.getJSONArray("chapters");
for (int index = 0; index < chapterArrayNodes.length(); index++) {
JSONArray currentChapterArray = chapterArrayNodes.getJSONArray(index);
Chapter currentChapter = constructChapterFromJSONArray(currentChapterArray, mangaName);
chapterList.add(currentChapter);
}
return chapterList;
}
private Chapter constructChapterFromJSONArray(JSONArray chapterNode, String mangaName) throws JSONException {
Chapter newChapter = DefaultFactory.Chapter.constructDefault();
newChapter.setUrl("https://www.mangaeden.com/api/chapter/" + chapterNode.getString(3) + "/");
newChapter.setName(mangaName + " " + chapterNode.getDouble(0));
newChapter.setDate(chapterNode.getLong(1) * 1000);
return newChapter;
}
private List<Chapter> setSourceForChapterList(List<Chapter> chapterList) {
for (Chapter currentChapter : chapterList) {
currentChapter.setSource(NAME);
}
return chapterList;
}
private List<Chapter> setParentUrlForChapterList(List<Chapter> chapterList, String parentUrl) {
for (Chapter currentChapter : chapterList) {
currentChapter.setParentUrl(parentUrl);
}
return chapterList;
}
private List<Chapter> setNumberForChapterList(List<Chapter> chapterList) {
Collections.reverse(chapterList);
for (int index = 0; index < chapterList.size(); index++) {
chapterList.get(index).setNumber(index + 1);
}
return chapterList;
}
private void saveChaptersToDatabase(List<Chapter> chapterList, String parentUrl) {
ApplicationSQLiteOpenHelper applicationSQLiteOpenHelper = ApplicationSQLiteOpenHelper.getInstance();
SQLiteDatabase sqLiteDatabase = applicationSQLiteOpenHelper.getWritableDatabase();
StringBuilder selection = new StringBuilder();
List<String> selectionArgs = new ArrayList<String>();
selection.append(ApplicationContract.Chapter.COLUMN_SOURCE + " = ?");
selectionArgs.add(NAME);
selection.append(" AND ").append(ApplicationContract.Chapter.COLUMN_PARENT_URL + " = ?");
selectionArgs.add(parentUrl);
sqLiteDatabase.beginTransaction();
try {
cupboard().withDatabase(sqLiteDatabase).delete(Chapter.class, selection.toString(), selectionArgs.toArray(new String[selectionArgs.size()]));
for (Chapter currentChapter : chapterList) {
cupboard().withDatabase(sqLiteDatabase).put(currentChapter);
}
sqLiteDatabase.setTransactionSuccessful();
} finally {
sqLiteDatabase.endTransaction();
}
}
@Override
public Observable<String> pullImageUrlsFromNetwork(final RequestWrapper request) {
return MangaService.getPermanentInstance()
.getResponse(request.getUrl())
.flatMap(new Func1<Response, Observable<String>>() {
@Override
public Observable<String> call(Response response) {
return MangaService.mapResponseToString(response);
}
})
.flatMap(new Func1<String, Observable<List<String>>>() {
@Override
public Observable<List<String>> call(final String unparsedJson) {
return Observable.create(new Observable.OnSubscribe<List<String>>() {
@Override
public void call(Subscriber<? super List<String>> subscriber) {
try {
subscriber.onNext(parseJsonToImageUrls(unparsedJson));
subscriber.onCompleted();
} catch (Throwable e) {
subscriber.onError(e);
}
}
});
}
})
.doOnNext(new Action1<List<String>>() {
@Override
public void call(List<String> imageUrls) {
CacheProvider.getInstance().putImageUrlsToDiskCache(request.getUrl(), imageUrls);
}
})
.flatMap(new Func1<List<String>, Observable<String>>() {
@Override
public Observable<String> call(List<String> imageUrls) {
return Observable.from(imageUrls.toArray(new String[imageUrls.size()]));
}
});
}
private List<String> parseJsonToImageUrls(String unparsedJson) throws JSONException{
JSONObject parsedJson = new JSONObject(unparsedJson);
List<String> imageUrlList = new ArrayList<String>();
JSONArray imageArrayNodes = parsedJson.getJSONArray("images");
for (int index = 0; index < imageArrayNodes.length(); index++) {
JSONArray currentImageNode = imageArrayNodes.getJSONArray(index);
imageUrlList.add("https://cdn.mangaeden.com/mangasimg/" + currentImageNode.getString(1));
}
Collections.reverse(imageUrlList);
return imageUrlList;
}
@Override
public Observable<String> recursivelyConstructDatabase(final String url) {
return MangaService.getPermanentInstance()
.getResponse(url)
.flatMap(new Func1<Response, Observable<String>>() {
@Override
public Observable<String> call(Response response) {
return MangaService.mapResponseToString(response);
}
})
.flatMap(new Func1<String, Observable<String>>() {
@Override
public Observable<String> call(final String unparsedJson) {
return Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
try {
subscriber.onNext(parseEnglish_MangaHere(unparsedJson));
subscriber.onCompleted();
} catch (Throwable e) {
subscriber.onError(e);
}
}
});
}
});
}
private static String INITIAL_DATABASE_URL = "https://www.mangaeden.com/api/list/1/";
private String parseEnglish_MangaHere(String unparsedJson) throws JSONException {
JSONObject parsedJson = new JSONObject(unparsedJson);
List<Manga> mangaList = new ArrayList<Manga>();
JSONArray mangaArrayNodes = parsedJson.getJSONArray("manga");
for (int index = 0; index < mangaArrayNodes.length(); index++) {
JSONObject currentMangaNode = mangaArrayNodes.getJSONObject(index);
Manga newManga = new Manga();
newManga.setSource(NAME);
newManga.setUrl("https://www.mangaeden.com/api/manga/" + currentMangaNode.getString("i") + "/");
newManga.setName(currentMangaNode.getString("t"));
newManga.setThumbnailUrl("https://cdn.mangaeden.com/mangasimg/" + currentMangaNode.getString("im"));
newManga.setCompleted(currentMangaNode.getInt("s") == 2);
newManga.setRank(currentMangaNode.getInt("h")); // Hits.
mangaList.add(newManga);
}
Collections.sort(mangaList, new Comparator<Manga>() {
@Override
public int compare(Manga lhs, Manga rhs) {
if (lhs.getRank() < rhs.getRank()) {
return 1;
} else if (lhs.getRank() == rhs.getRank()) {
return 0;
} else {
return -1;
}
}
});
for (int index = 0; index < mangaList.size(); index++) {
mangaList.get(index).setRank(index + 1);
}
LibrarySQLiteOpenHelper librarySQLiteOpenHelper = LibrarySQLiteOpenHelper.getInstance();
SQLiteDatabase sqLiteDatabase = librarySQLiteOpenHelper.getWritableDatabase();
sqLiteDatabase.beginTransaction();
try {
for (Manga currentManga : mangaList) {
cupboard().withDatabase(sqLiteDatabase).put(currentManga);
}
sqLiteDatabase.setTransactionSuccessful();
} finally {
sqLiteDatabase.endTransaction();
}
return null;
}
}