Initial commit for categories

This commit is contained in:
inorichi 2015-12-23 22:30:48 +01:00
parent 72b2471619
commit 1c4b5b3a72
16 changed files with 401 additions and 24 deletions

View file

@ -16,23 +16,34 @@ import com.pushtorefresh.storio.sqlite.queries.RawQuery;
import java.util.List;
import eu.kanade.mangafeed.data.database.models.MangaSync;
import eu.kanade.mangafeed.data.mangasync.base.MangaSyncService;
import eu.kanade.mangafeed.data.database.models.Category;
import eu.kanade.mangafeed.data.database.models.CategoryStorIOSQLiteDeleteResolver;
import eu.kanade.mangafeed.data.database.models.CategoryStorIOSQLiteGetResolver;
import eu.kanade.mangafeed.data.database.models.CategoryStorIOSQLitePutResolver;
import eu.kanade.mangafeed.data.database.models.Chapter;
import eu.kanade.mangafeed.data.database.models.ChapterStorIOSQLiteDeleteResolver;
import eu.kanade.mangafeed.data.database.models.ChapterStorIOSQLiteGetResolver;
import eu.kanade.mangafeed.data.database.models.ChapterStorIOSQLitePutResolver;
import eu.kanade.mangafeed.data.database.models.Manga;
import eu.kanade.mangafeed.data.database.models.MangaCategory;
import eu.kanade.mangafeed.data.database.models.MangaCategoryStorIOSQLiteDeleteResolver;
import eu.kanade.mangafeed.data.database.models.MangaCategoryStorIOSQLiteGetResolver;
import eu.kanade.mangafeed.data.database.models.MangaCategoryStorIOSQLitePutResolver;
import eu.kanade.mangafeed.data.database.models.MangaStorIOSQLiteDeleteResolver;
import eu.kanade.mangafeed.data.database.models.MangaStorIOSQLiteGetResolver;
import eu.kanade.mangafeed.data.database.models.MangaStorIOSQLitePutResolver;
import eu.kanade.mangafeed.data.database.models.MangaSync;
import eu.kanade.mangafeed.data.database.models.MangaSyncStorIOSQLiteDeleteResolver;
import eu.kanade.mangafeed.data.database.models.MangaSyncStorIOSQLiteGetResolver;
import eu.kanade.mangafeed.data.database.models.MangaSyncStorIOSQLitePutResolver;
import eu.kanade.mangafeed.data.database.resolvers.LibraryMangaGetResolver;
import eu.kanade.mangafeed.data.database.resolvers.MangaWithUnreadGetResolver;
import eu.kanade.mangafeed.data.database.tables.MangaSyncTable;
import eu.kanade.mangafeed.data.database.tables.CategoryTable;
import eu.kanade.mangafeed.data.database.tables.ChapterTable;
import eu.kanade.mangafeed.data.database.tables.MangaCategoryTable;
import eu.kanade.mangafeed.data.database.tables.MangaSyncTable;
import eu.kanade.mangafeed.data.database.tables.MangaTable;
import eu.kanade.mangafeed.data.mangasync.base.MangaSyncService;
import eu.kanade.mangafeed.util.ChapterRecognition;
import eu.kanade.mangafeed.util.PostResult;
import rx.Observable;
@ -60,6 +71,16 @@ public class DatabaseHelper {
.getResolver(new MangaSyncStorIOSQLiteGetResolver())
.deleteResolver(new MangaSyncStorIOSQLiteDeleteResolver())
.build())
.addTypeMapping(Category.class, SQLiteTypeMapping.<Category>builder()
.putResolver(new CategoryStorIOSQLitePutResolver())
.getResolver(new CategoryStorIOSQLiteGetResolver())
.deleteResolver(new CategoryStorIOSQLiteDeleteResolver())
.build())
.addTypeMapping(MangaCategory.class, SQLiteTypeMapping.<MangaCategory>builder()
.putResolver(new MangaCategoryStorIOSQLitePutResolver())
.getResolver(new MangaCategoryStorIOSQLiteGetResolver())
.deleteResolver(new MangaCategoryStorIOSQLiteDeleteResolver())
.build())
.build();
}
@ -79,6 +100,37 @@ public class DatabaseHelper {
MangaTable.COLUMN_TITLE
);
private final String libraryMangaQuery = String.format(
"SELECT M.*, COALESCE(MC.%10$s, 0) AS %12$s " +
"FROM (" +
"SELECT %1$s.*, COALESCE(C.unread, 0) AS %6$s " +
"FROM %1$s " +
"LEFT JOIN (" +
"SELECT %5$s, COUNT(*) AS unread " +
"FROM %2$s " +
"WHERE %7$s = 0 " +
"GROUP BY %5$s" +
") AS C " +
"ON %4$s = C.%5$s " +
"WHERE %8$s = 1 " +
"GROUP BY %4$s " +
"ORDER BY %9$s" +
") AS M " +
"LEFT JOIN (SELECT * FROM %3$s) AS MC ON MC.%11$s = M.%4$s",
MangaTable.TABLE,
ChapterTable.TABLE,
MangaCategoryTable.TABLE,
MangaTable.COLUMN_ID,
ChapterTable.COLUMN_MANGA_ID,
MangaTable.COLUMN_UNREAD,
ChapterTable.COLUMN_READ,
MangaTable.COLUMN_FAVORITE,
MangaTable.COLUMN_TITLE,
MangaCategoryTable.COLUMN_CATEGORY_ID,
MangaCategoryTable.COLUMN_MANGA_ID,
MangaTable.COLUMN_CATEGORY
);
public PreparedGetListOfObjects<Manga> getMangas() {
return db.get()
.listOfObjects(Manga.class)
@ -95,7 +147,18 @@ public class DatabaseHelper {
.query(favoriteMangasWithUnreadQuery)
.observesTables(MangaTable.TABLE, ChapterTable.TABLE)
.build())
.withGetResolver(MangaWithUnreadGetResolver.instance)
.withGetResolver(MangaWithUnreadGetResolver.INSTANCE)
.prepare();
}
public PreparedGetListOfObjects<Manga> getLibraryMangas() {
return db.get()
.listOfObjects(Manga.class)
.withQuery(RawQuery.builder()
.query(libraryMangaQuery)
.observesTables(MangaTable.TABLE, ChapterTable.TABLE, CategoryTable.TABLE)
.build())
.withGetResolver(LibraryMangaGetResolver.INSTANCE)
.prepare();
}
@ -337,4 +400,39 @@ public class DatabaseHelper {
.object(manga)
.prepare();
}
// Categories related queries
public PreparedGetListOfObjects<Category> getCategories() {
return db.get()
.listOfObjects(Category.class)
.withQuery(Query.builder()
.table(CategoryTable.TABLE)
.build())
.prepare();
}
public PreparedPutObject<Category> insertCategory(Category category) {
return db.put()
.object(category)
.prepare();
}
public PreparedDeleteObject<Category> deleteCategory(Category category) {
return db.delete()
.object(category)
.prepare();
}
public PreparedPutObject<MangaCategory> insertMangaCategory(MangaCategory mangaCategory) {
return db.put()
.object(mangaCategory)
.prepare();
}
public PreparedPutCollectionOfObjects<MangaCategory> insertMangasCategory(List<MangaCategory> mangasCategory) {
return db.put()
.objects(mangasCategory)
.prepare();
}
}

View file

@ -5,6 +5,8 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.support.annotation.NonNull;
import eu.kanade.mangafeed.data.database.tables.CategoryTable;
import eu.kanade.mangafeed.data.database.tables.MangaCategoryTable;
import eu.kanade.mangafeed.data.database.tables.MangaSyncTable;
import eu.kanade.mangafeed.data.database.tables.ChapterTable;
import eu.kanade.mangafeed.data.database.tables.MangaTable;
@ -12,7 +14,7 @@ import eu.kanade.mangafeed.data.database.tables.MangaTable;
public class DbOpenHelper extends SQLiteOpenHelper {
public static final String DATABASE_NAME = "mangafeed.db";
public static final int DATABASE_VERSION = 3;
public static final int DATABASE_VERSION = 1;
public DbOpenHelper(@NonNull Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
@ -23,16 +25,22 @@ public class DbOpenHelper extends SQLiteOpenHelper {
db.execSQL(MangaTable.getCreateTableQuery());
db.execSQL(ChapterTable.getCreateTableQuery());
db.execSQL(MangaSyncTable.getCreateTableQuery());
db.execSQL(CategoryTable.getCreateTableQuery());
db.execSQL(MangaCategoryTable.getCreateTableQuery());
// DB indexes
db.execSQL(MangaTable.getCreateUrlIndexQuery());
db.execSQL(MangaTable.getCreateFavoriteIndexQuery());
db.execSQL(ChapterTable.getCreateMangaIdIndexQuery());
}
@Override
public void onUpgrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion < 3)
db.execSQL(MangaSyncTable.getCreateTableQuery());
}
@Override
public void onConfigure(SQLiteDatabase db){
public void onConfigure(@NonNull SQLiteDatabase db) {
db.setForeignKeyConstraintsEnabled(true);
}

View file

@ -0,0 +1,26 @@
package eu.kanade.mangafeed.data.database.models;
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteColumn;
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType;
import java.io.Serializable;
import eu.kanade.mangafeed.data.database.tables.CategoryTable;
@StorIOSQLiteType(table = CategoryTable.TABLE)
public class Category implements Serializable {
@StorIOSQLiteColumn(name = CategoryTable.COLUMN_ID, key = true)
public Long id;
@StorIOSQLiteColumn(name = CategoryTable.COLUMN_NAME)
public String name;
public Category() {}
public static Category create(String name) {
Category c = new Category();
c.name = name;
return c;
}
}

View file

@ -40,7 +40,6 @@ public class Chapter implements Serializable {
public int status;
public Chapter() {}
public void setUrl(String url) {

View file

@ -36,7 +36,7 @@ public class Manga implements Serializable {
public String title;
@StorIOSQLiteColumn(name = MangaTable.COLUMN_STATUS)
public String status;
public int status;
@StorIOSQLiteColumn(name = MangaTable.COLUMN_THUMBNAIL_URL)
public String thumbnail_url;
@ -58,6 +58,8 @@ public class Manga implements Serializable {
public int unread;
public long category;
public Manga() {}
public void setUrl(String url) {
@ -83,12 +85,11 @@ public class Manga implements Serializable {
if (network.genre != null)
local.genre = network.genre;
if (network.status != null)
local.status = network.status;
if (network.thumbnail_url != null)
local.thumbnail_url = network.thumbnail_url;
local.status = network.status;
local.initialized = true;
}

View file

@ -0,0 +1,29 @@
package eu.kanade.mangafeed.data.database.models;
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteColumn;
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType;
import eu.kanade.mangafeed.data.database.tables.MangaCategoryTable;
@StorIOSQLiteType(table = MangaCategoryTable.TABLE)
public class MangaCategory {
@StorIOSQLiteColumn(name = MangaCategoryTable.COLUMN_ID, key = true)
public Long id;
@StorIOSQLiteColumn(name = MangaCategoryTable.COLUMN_MANGA_ID)
public long manga_id;
@StorIOSQLiteColumn(name = MangaCategoryTable.COLUMN_CATEGORY_ID)
public long category_id;
public MangaCategory() {}
public static MangaCategory create(Manga manga, Category category) {
MangaCategory mc = new MangaCategory();
mc.manga_id = manga.id;
mc.category_id = category.id;
return mc;
}
}

View file

@ -0,0 +1,28 @@
package eu.kanade.mangafeed.data.database.resolvers;
import android.database.Cursor;
import android.support.annotation.NonNull;
import eu.kanade.mangafeed.data.database.models.Manga;
import eu.kanade.mangafeed.data.database.models.MangaStorIOSQLiteGetResolver;
import eu.kanade.mangafeed.data.database.tables.MangaTable;
public class LibraryMangaGetResolver extends MangaStorIOSQLiteGetResolver {
public static final LibraryMangaGetResolver INSTANCE = new LibraryMangaGetResolver();
@Override
@NonNull
public Manga mapFromCursor(@NonNull Cursor cursor) {
Manga manga = super.mapFromCursor(cursor);
int unreadColumn = cursor.getColumnIndex(MangaTable.COLUMN_UNREAD);
manga.unread = cursor.getInt(unreadColumn);
int categoryColumn = cursor.getColumnIndex(MangaTable.COLUMN_CATEGORY);
manga.category = cursor.getLong(categoryColumn);
return manga;
}
}

View file

@ -9,7 +9,7 @@ import eu.kanade.mangafeed.data.database.tables.MangaTable;
public class MangaWithUnreadGetResolver extends MangaStorIOSQLiteGetResolver {
public static final MangaWithUnreadGetResolver instance = new MangaWithUnreadGetResolver();
public static final MangaWithUnreadGetResolver INSTANCE = new MangaWithUnreadGetResolver();
@Override
@NonNull

View file

@ -12,4 +12,20 @@ public class CategoryTable {
@NonNull
public static final String COLUMN_NAME = "name";
// This is just class with Meta Data, we don't need instances
private CategoryTable() {
throw new IllegalStateException("No instances please");
}
// Better than static final field -> allows VM to unload useless String
// Because you need this string only once per application life on the device
@NonNull
public static String getCreateTableQuery() {
return "CREATE TABLE " + TABLE + "("
+ COLUMN_ID + " INTEGER NOT NULL PRIMARY KEY, "
+ COLUMN_NAME + " TEXT NOT NULL"
+ ");";
}
}

View file

@ -48,8 +48,12 @@ public class ChapterTable {
+ COLUMN_DATE_UPLOAD + " LONG NOT NULL, "
+ "FOREIGN KEY(" + COLUMN_MANGA_ID + ") REFERENCES " + MangaTable.TABLE + "(" + MangaTable.COLUMN_ID + ") "
+ "ON DELETE CASCADE"
+ ");"
+ "CREATE INDEX " + TABLE + "_" + COLUMN_MANGA_ID + "_index ON " + TABLE + "(" + COLUMN_MANGA_ID + ");";
+ ");";
}
public static String getCreateMangaIdIndexQuery() {
return "CREATE INDEX " + TABLE + "_" + COLUMN_MANGA_ID + "_index ON " + TABLE + "(" + COLUMN_MANGA_ID + ");";
}
}

View file

@ -8,8 +8,33 @@ public class MangaCategoryTable {
public static final String TABLE = "mangas_categories";
@NonNull
public static final String COLUMN_MANGA_ID = "_manga_id";
public static final String COLUMN_ID = "_id";
@NonNull
public static final String COLUMN_CATEGORY_ID = "_category_id";
public static final String COLUMN_MANGA_ID = "manga_id";
@NonNull
public static final String COLUMN_CATEGORY_ID = "category_id";
// This is just class with Meta Data, we don't need instances
private MangaCategoryTable() {
throw new IllegalStateException("No instances please");
}
// Better than static final field -> allows VM to unload useless String
// Because you need this string only once per application life on the device
@NonNull
public static String getCreateTableQuery() {
return "CREATE TABLE " + TABLE + "("
+ COLUMN_ID + " INTEGER NOT NULL PRIMARY KEY, "
+ COLUMN_MANGA_ID + " INTEGER NOT NULL, "
+ COLUMN_CATEGORY_ID + " INTEGER NOT NULL, "
+ "FOREIGN KEY(" + COLUMN_CATEGORY_ID + ") REFERENCES " + CategoryTable.TABLE + "(" + CategoryTable.COLUMN_ID + ") "
+ "ON DELETE CASCADE, "
+ "FOREIGN KEY(" + COLUMN_MANGA_ID + ") REFERENCES " + MangaTable.TABLE + "(" + MangaTable.COLUMN_ID + ") "
+ "ON DELETE CASCADE"
+ ");";
}
}

View file

@ -50,11 +50,14 @@ public class MangaTable {
public static final String COLUMN_VIEWER = "viewer";
@NonNull
public static final String COLUMN_CHAPTER_FLAGS = "chapter_order";
public static final String COLUMN_CHAPTER_FLAGS = "chapter_flags";
@NonNull
public static final String COLUMN_UNREAD = "unread";
@NonNull
public static final String COLUMN_CATEGORY = "category";
// This is just class with Meta Data, we don't need instances
private MangaTable() {
throw new IllegalStateException("No instances please");
@ -73,15 +76,23 @@ public class MangaTable {
+ COLUMN_DESCRIPTION + " TEXT, "
+ COLUMN_GENRE + " TEXT, "
+ COLUMN_TITLE + " TEXT NOT NULL, "
+ COLUMN_STATUS + " TEXT, "
+ COLUMN_STATUS + " INTEGER NOT NULL, "
+ COLUMN_THUMBNAIL_URL + " TEXT, "
+ COLUMN_FAVORITE + " INTEGER NOT NULL, "
+ COLUMN_LAST_UPDATE + " LONG, "
+ COLUMN_INITIALIZED + " BOOLEAN NOT NULL, "
+ COLUMN_VIEWER + " INTEGER NOT NULL, "
+ COLUMN_CHAPTER_FLAGS + " INTEGER NOT NULL"
+ ");"
+ "CREATE INDEX " + TABLE + "_" + COLUMN_URL + "_index ON " + TABLE + "(" + COLUMN_URL + ");";
+ ");";
}
public static String getCreateUrlIndexQuery() {
return "CREATE INDEX " + TABLE + "_" + COLUMN_URL + "_index ON " + TABLE + "(" + COLUMN_URL + ");";
}
public static String getCreateFavoriteIndexQuery() {
return "CREATE INDEX " + TABLE + "_" + COLUMN_FAVORITE + "_index ON " + TABLE + "(" + COLUMN_FAVORITE + ");";
}
}

View file

@ -296,7 +296,7 @@ public class Batoto extends Source {
boolean fieldCompleted = unparsedHtml.contains("<td>Complete</td>");
//TODO fix
newManga.status = fieldCompleted + "";
newManga.status = 0;
newManga.initialized = true;

View file

@ -249,7 +249,8 @@ public class Mangahere extends Source {
}
if (statusElement != null) {
boolean fieldCompleted = statusElement.text().contains("Completed");
newManga.status = fieldCompleted + "";
// TODO fix status
// newManga.status = fieldCompleted + "";
}
beginIndex = unparsedHtml.indexOf("<img");

View file

@ -1,6 +1,10 @@
package eu.kanade.mangafeed.ui.library;
import android.os.Bundle;
import android.util.Pair;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
@ -44,4 +48,12 @@ public class LibraryPresenter extends BasePresenter<LibraryFragment> {
.subscribe());
}
public Observable<Map<Long, List<Manga>>> getLibraryMangasObservable() {
return db.getLibraryMangas().createObservable()
.flatMap(mangas -> Observable.from(mangas)
.groupBy(manga -> manga.category)
.flatMap(group -> group.toList().map(list -> new Pair<>(group.getKey(), list)))
.toMap(pair -> pair.first, pair -> pair.second));
}
}

View file

@ -0,0 +1,119 @@
package eu.kanade.mangafeed;
import android.app.Application;
import android.os.Build;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import java.util.List;
import eu.kanade.mangafeed.data.database.DatabaseHelper;
import eu.kanade.mangafeed.data.database.models.Category;
import eu.kanade.mangafeed.data.database.models.Manga;
import eu.kanade.mangafeed.data.database.models.MangaCategory;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
@Config(constants = BuildConfig.class, sdk = Build.VERSION_CODES.LOLLIPOP)
@RunWith(RobolectricGradleTestRunner.class)
public class CategoryTest {
DatabaseHelper db;
@Before
public void setup() {
Application app = RuntimeEnvironment.application;
db = new DatabaseHelper(app);
// Create 5 mangas
createManga("a");
createManga("b");
createManga("c");
createManga("d");
createManga("e");
}
@Test
public void testHasCategories() {
// Create 2 categories
createCategory("Reading");
createCategory("Hold");
List<Category> categories = db.getCategories().executeAsBlocking();
assertThat(categories, hasSize(2));
}
@Test
public void testHasLibraryMangas() {
List<Manga> mangas = db.getLibraryMangas().executeAsBlocking();
assertThat(mangas, hasSize(5));
}
@Test
public void testHasCorrectFavorites() {
Manga m = new Manga();
m.title = "title";
m.author = "";
m.artist = "";
m.thumbnail_url = "";
m.genre = "a list of genres";
m.description = "long description";
m.url = "url to manga";
m.favorite = false;
db.insertManga(m).executeAsBlocking();
List<Manga> mangas = db.getLibraryMangas().executeAsBlocking();
assertThat(mangas, hasSize(5));
}
@Test
public void testMangaInCategory() {
// Create 2 categories
createCategory("Reading");
createCategory("Hold");
// It should not have 0 as id
Category c = db.getCategories().executeAsBlocking().get(0);
assertThat(c.id, not(0));
// Add a manga to a category
Manga m = db.getMangas().executeAsBlocking().get(0);
MangaCategory mc = MangaCategory.create(m, c);
db.insertMangaCategory(mc).executeAsBlocking();
// Get mangas from library and assert manga category is the same
List<Manga> mangas = db.getLibraryMangas().executeAsBlocking();
for (Manga manga : mangas) {
if (manga.id.equals(m.id)) {
assertThat(manga.category, is(c.id));
}
}
}
private void createManga(String title) {
Manga m = new Manga();
m.title = title;
m.author = "";
m.artist = "";
m.thumbnail_url = "";
m.genre = "a list of genres";
m.description = "long description";
m.url = "url to manga";
m.favorite = true;
db.insertManga(m).executeAsBlocking();
}
private void createCategory(String name) {
Category c = new Category();
c.name = name;
db.insertCategory(c).executeAsBlocking();
}
}