Initial MAL support

This commit is contained in:
inorichi
2015-11-25 16:08:24 +01:00
parent da7d5886da
commit ee7d76e775
33 changed files with 1037 additions and 168 deletions

View File

@@ -0,0 +1,16 @@
package eu.kanade.mangafeed.data.chaptersync;
import rx.Observable;
public abstract class BaseChapterSync {
// Name of the chapter sync service to display
public abstract String getName();
// Id of the sync service (must be declared and obtained from ChapterSyncManager to avoid conflicts)
public abstract int getId();
public abstract Observable<Boolean> login(String username, String password);
public abstract boolean isLogged();
}

View File

@@ -0,0 +1,29 @@
package eu.kanade.mangafeed.data.chaptersync;
import android.content.Context;
import java.util.ArrayList;
import java.util.List;
public class ChapterSyncManager {
private List<BaseChapterSync> services;
private MyAnimeList myAnimeList;
public static final int MYANIMELIST = 1;
public ChapterSyncManager(Context context) {
services = new ArrayList<>();
myAnimeList = new MyAnimeList(context);
services.add(myAnimeList);
}
public MyAnimeList getMyAnimeList() {
return myAnimeList;
}
public List<BaseChapterSync> getChapterSyncServices() {
return services;
}
}

View File

@@ -0,0 +1,163 @@
package eu.kanade.mangafeed.data.chaptersync;
import android.content.Context;
import android.net.Uri;
import android.util.Xml;
import com.squareup.okhttp.Credentials;
import com.squareup.okhttp.FormEncodingBuilder;
import com.squareup.okhttp.Headers;
import com.squareup.okhttp.Response;
import org.jsoup.Jsoup;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.io.StringWriter;
import java.util.List;
import javax.inject.Inject;
import eu.kanade.mangafeed.App;
import eu.kanade.mangafeed.data.database.models.ChapterSync;
import eu.kanade.mangafeed.data.network.NetworkHelper;
import eu.kanade.mangafeed.data.preference.PreferencesHelper;
import rx.Observable;
public class MyAnimeList extends BaseChapterSync {
@Inject PreferencesHelper preferences;
@Inject NetworkHelper networkService;
private Headers headers;
public static final String BASE_URL = "http://myanimelist.net";
private static final String ENTRY = "entry";
private static final String CHAPTER = "chapter";
public MyAnimeList(Context context) {
App.get(context).getComponent().inject(this);
String username = preferences.getChapterSyncUsername(this);
String password = preferences.getChapterSyncPassword(this);
if (!username.isEmpty() && !password.isEmpty()) {
createHeaders(username, password);
}
}
@Override
public String getName() {
return "MyAnimeList";
}
@Override
public int getId() {
return ChapterSyncManager.MYANIMELIST;
}
public String getLoginUrl() {
return Uri.parse(BASE_URL).buildUpon()
.appendEncodedPath("api/account/verify_credentials.xml")
.toString();
}
public Observable<Boolean> login(String username, String password) {
createHeaders(username, password);
return networkService.getResponse(getLoginUrl(), headers, null)
.map(response -> response.code() == 200);
}
@Override
public boolean isLogged() {
return !preferences.getChapterSyncUsername(this).isEmpty()
&& !preferences.getChapterSyncPassword(this).isEmpty();
}
public String getSearchUrl(String query) {
return Uri.parse(BASE_URL).buildUpon()
.appendEncodedPath("api/manga/search.xml")
.appendQueryParameter("q", query)
.toString();
}
public Observable<List<ChapterSync>> search(String query) {
return networkService.getStringResponse(getSearchUrl(query), headers, null)
.map(Jsoup::parse)
.flatMap(doc -> Observable.from(doc.select("entry")))
.map(entry -> {
ChapterSync chapter = ChapterSync.create(this);
chapter.title = entry.select("title").first().text();
chapter.remote_id = Long.parseLong(entry.select("id").first().text());
return chapter;
})
.toList();
}
public String getListUrl(String username) {
return Uri.parse(BASE_URL).buildUpon()
.appendPath("malappinfo.php")
.appendQueryParameter("u", username)
.appendQueryParameter("status", "all")
.appendQueryParameter("type", "manga")
.toString();
}
public Observable<List<ChapterSync>> getList(String username) {
return networkService.getStringResponse(getListUrl(username), headers, null)
.map(Jsoup::parse)
.flatMap(doc -> Observable.from(doc.select("manga")))
.map(entry -> {
ChapterSync chapter = ChapterSync.create(this);
chapter.title = entry.select("series_title").first().text();
chapter.remote_id = Long.parseLong(
entry.select("series_mangadb_id").first().text());
chapter.last_chapter_read = Integer.parseInt(
entry.select("my_read_chapters").first().text());
return chapter;
})
.toList();
}
public String getUpdateUrl(ChapterSync chapter) {
return Uri.parse(BASE_URL).buildUpon()
.appendEncodedPath("api/mangalist/update")
.appendPath(chapter.remote_id + ".xml")
.toString();
}
public Observable<Response> update(ChapterSync chapter) {
XmlSerializer xml = Xml.newSerializer();
StringWriter writer = new StringWriter();
try {
xml.setOutput(writer);
xml.startDocument("UTF-8", false);
xml.startTag("", ENTRY);
xml.startTag("", CHAPTER);
xml.text(chapter.last_chapter_read + "");
xml.endTag("", CHAPTER);
xml.endTag("", ENTRY);
xml.endDocument();
} catch (IOException e) {
return Observable.error(e);
}
FormEncodingBuilder form = new FormEncodingBuilder();
form.add("data", writer.toString());
return networkService.postData(getUpdateUrl(chapter), form.build(), headers);
}
public void createHeaders(String username, String password) {
Headers.Builder builder = new Headers.Builder();
builder.add("Authorization", Credentials.basic(username, password));
// builder.add("User-Agent", "");
setHeaders(builder.build());
}
public void setHeaders(Headers headers) {
this.headers = headers;
}
}

View File

@@ -16,14 +16,20 @@ import com.pushtorefresh.storio.sqlite.queries.RawQuery;
import java.util.List;
import eu.kanade.mangafeed.data.chaptersync.BaseChapterSync;
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.ChapterSync;
import eu.kanade.mangafeed.data.database.models.ChapterSyncStorIOSQLiteDeleteResolver;
import eu.kanade.mangafeed.data.database.models.ChapterSyncStorIOSQLiteGetResolver;
import eu.kanade.mangafeed.data.database.models.ChapterSyncStorIOSQLitePutResolver;
import eu.kanade.mangafeed.data.database.models.Manga;
import eu.kanade.mangafeed.data.database.models.MangaStorIOSQLiteDeleteResolver;
import eu.kanade.mangafeed.data.database.models.MangaStorIOSQLitePutResolver;
import eu.kanade.mangafeed.data.database.resolvers.MangaWithUnreadGetResolver;
import eu.kanade.mangafeed.data.database.tables.ChapterSyncTable;
import eu.kanade.mangafeed.data.database.tables.ChapterTable;
import eu.kanade.mangafeed.data.database.tables.MangaTable;
import eu.kanade.mangafeed.util.ChapterRecognition;
@@ -48,6 +54,11 @@ public class DatabaseHelper {
.getResolver(new ChapterStorIOSQLiteGetResolver())
.deleteResolver(new ChapterStorIOSQLiteDeleteResolver())
.build())
.addTypeMapping(ChapterSync.class, SQLiteTypeMapping.<ChapterSync>builder()
.putResolver(new ChapterSyncStorIOSQLitePutResolver())
.getResolver(new ChapterSyncStorIOSQLiteGetResolver())
.deleteResolver(new ChapterSyncStorIOSQLiteDeleteResolver())
.build())
.build();
}
@@ -263,4 +274,31 @@ public class DatabaseHelper {
.objects(chapters)
.prepare();
}
// Chapter sync related queries
public PreparedGetListOfObjects<ChapterSync> getChapterSync(Manga manga, BaseChapterSync sync) {
return db.get()
.listOfObjects(ChapterSync.class)
.withQuery(Query.builder()
.table(ChapterSyncTable.TABLE)
.where(ChapterSyncTable.COLUMN_MANGA_ID + "=? AND " +
ChapterSyncTable.COLUMN_SYNC_ID + "=?")
.whereArgs(manga.id, sync.getId())
.build())
.prepare();
}
public PreparedPutObject<ChapterSync> insertChapterSync(ChapterSync chapter) {
return db.put()
.object(chapter)
.prepare();
}
public PreparedDeleteObject<ChapterSync> deleteChapterSync(ChapterSync chapter) {
return db.delete()
.object(chapter)
.prepare();
}
}

View File

@@ -5,13 +5,14 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.support.annotation.NonNull;
import eu.kanade.mangafeed.data.database.tables.ChapterSyncTable;
import eu.kanade.mangafeed.data.database.tables.ChapterTable;
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 = 1;
public static final int DATABASE_VERSION = 2;
public DbOpenHelper(@NonNull Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
@@ -25,7 +26,8 @@ public class DbOpenHelper extends SQLiteOpenHelper {
@Override
public void onUpgrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) {
// no impl
if (oldVersion == 1)
db.execSQL(ChapterSyncTable.getCreateTableQuery());
}
@Override

View File

@@ -0,0 +1,35 @@
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.chaptersync.BaseChapterSync;
import eu.kanade.mangafeed.data.database.tables.ChapterSyncTable;
@StorIOSQLiteType(table = ChapterSyncTable.TABLE)
public class ChapterSync {
@StorIOSQLiteColumn(name = ChapterSyncTable.COLUMN_ID, key = true)
public long id;
@StorIOSQLiteColumn(name = ChapterSyncTable.COLUMN_MANGA_ID)
public long manga_id;
@StorIOSQLiteColumn(name = ChapterSyncTable.COLUMN_SYNC_ID)
public long sync_id;
@StorIOSQLiteColumn(name = ChapterSyncTable.COLUMN_REMOTE_ID)
public long remote_id;
@StorIOSQLiteColumn(name = ChapterSyncTable.COLUMN_TITLE)
public String title;
@StorIOSQLiteColumn(name = ChapterSyncTable.COLUMN_LAST_CHAPTER_READ)
public int last_chapter_read;
public static ChapterSync create(BaseChapterSync sync) {
ChapterSync chapter = new ChapterSync();
chapter.sync_id = sync.getId();
return chapter;
}
}

View File

@@ -0,0 +1,35 @@
package eu.kanade.mangafeed.data.database.tables;
import android.support.annotation.NonNull;
public class ChapterSyncTable {
public static final String TABLE = "chapter_sync";
public static final String COLUMN_ID = "_id";
public static final String COLUMN_MANGA_ID = "manga_id";
public static final String COLUMN_SYNC_ID = "sync_id";
public static final String COLUMN_REMOTE_ID = "remote_id";
public static final String COLUMN_TITLE = "title";
public static final String COLUMN_LAST_CHAPTER_READ = "last_chapter_read";
@NonNull
public static String getCreateTableQuery() {
return "CREATE TABLE " + TABLE + "("
+ COLUMN_ID + " INTEGER NOT NULL PRIMARY KEY, "
+ COLUMN_MANGA_ID + " INTEGER NOT NULL, "
+ COLUMN_SYNC_ID + " INTEGER NOT NULL, "
+ COLUMN_REMOTE_ID + " INTEGER NOT NULL, "
+ COLUMN_TITLE + " TEXT NOT NULL, "
+ COLUMN_LAST_CHAPTER_READ + " INTEGER NOT NULL, "
+ "FOREIGN KEY(" + COLUMN_MANGA_ID + ") REFERENCES " + MangaTable.TABLE + "(" + MangaTable.COLUMN_ID + ") "
+ "ON DELETE CASCADE"
+ ");";
}
}

View File

@@ -8,6 +8,7 @@ import com.f2prateek.rx.preferences.Preference;
import com.f2prateek.rx.preferences.RxSharedPreferences;
import eu.kanade.mangafeed.R;
import eu.kanade.mangafeed.data.chaptersync.BaseChapterSync;
import eu.kanade.mangafeed.data.source.base.Source;
import eu.kanade.mangafeed.util.DiskUtils;
import rx.Observable;
@@ -20,6 +21,8 @@ public class PreferencesHelper {
private static final String SOURCE_ACCOUNT_USERNAME = "pref_source_username_";
private static final String SOURCE_ACCOUNT_PASSWORD = "pref_source_password_";
private static final String CHAPTERSYNC_ACCOUNT_USERNAME = "pref_chaptersync_username_";
private static final String CHAPTERSYNC_ACCOUNT_PASSWORD = "pref_chaptersync_password_";
public PreferencesHelper(Context context) {
this.context = context;
@@ -84,6 +87,21 @@ public class PreferencesHelper {
.apply();
}
public String getChapterSyncUsername(BaseChapterSync sync) {
return prefs.getString(CHAPTERSYNC_ACCOUNT_USERNAME + sync.getId(), "");
}
public String getChapterSyncPassword(BaseChapterSync sync) {
return prefs.getString(CHAPTERSYNC_ACCOUNT_PASSWORD + sync.getId(), "");
}
public void setChapterSyncCredentials(BaseChapterSync sync, String username, String password) {
prefs.edit()
.putString(CHAPTERSYNC_ACCOUNT_USERNAME + sync.getId(), username)
.putString(CHAPTERSYNC_ACCOUNT_PASSWORD + sync.getId(), password)
.apply();
}
public String getDownloadsDirectory() {
return prefs.getString(getKey(R.string.pref_download_directory_key),
DiskUtils.getStorageDirectories(context)[0]);