mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-30 22:07:57 +01:00 
			
		
		
		
	Add favorites downloader.
This commit is contained in:
		| @@ -22,7 +22,10 @@ import java.util.* | ||||
| import exh.ui.login.LoginActivity | ||||
| import exh.util.UriFilter | ||||
| import exh.util.UriGroup | ||||
| import okhttp3.CacheControl | ||||
| import okhttp3.Headers | ||||
| import okhttp3.Request | ||||
| import org.jsoup.nodes.Document | ||||
|  | ||||
| class EHentai(override val id: Long, | ||||
|               val exh: Boolean, | ||||
| @@ -38,7 +41,7 @@ class EHentai(override val id: Long, | ||||
|         get() = if(exh) | ||||
|             "$schema://exhentai.org" | ||||
|         else | ||||
|             "http://e-hentai.org" | ||||
|             "$schema://e-hentai.org" | ||||
|  | ||||
|     override val lang = "all" | ||||
|     override val supportsLatest = true | ||||
| @@ -52,11 +55,8 @@ class EHentai(override val id: Long, | ||||
|      */ | ||||
|     data class ParsedManga(val fav: String?, val manga: Manga) | ||||
|  | ||||
|     /** | ||||
|      * Parse a list of galleries | ||||
|      */ | ||||
|     fun genericMangaParse(response: Response) | ||||
|             = with(response.asJsoup()) { | ||||
|     fun extendedGenericMangaParse(doc: Document) | ||||
|             = with(doc) { | ||||
|         //Parse mangas | ||||
|         val parsedMangas = select(".gtr0,.gtr1").map { | ||||
|             ParsedManga( | ||||
| @@ -84,7 +84,15 @@ class EHentai(override val id: Long, | ||||
|         val hasNextPage = select("a[onclick=return false]").last()?.let { | ||||
|             it.text() == ">" | ||||
|         } ?: false | ||||
|         MangasPage(parsedMangas.map { it.manga }, hasNextPage) | ||||
|         Pair(parsedMangas, hasNextPage) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Parse a list of galleries | ||||
|      */ | ||||
|     fun genericMangaParse(response: Response) | ||||
|             = extendedGenericMangaParse(response.asJsoup()).let { | ||||
|         MangasPage(it.first.map { it.manga }, it.second) | ||||
|     } | ||||
|  | ||||
|     override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> | ||||
| @@ -143,16 +151,29 @@ class EHentai(override val id: Long, | ||||
|         return exGet(uri.toString(), page) | ||||
|     } | ||||
|  | ||||
|     override fun latestUpdatesRequest(page: Int) = exGet(baseUrl, page) | ||||
|     override fun latestUpdatesRequest(page: Int) = exGet(baseUrl, page)!! | ||||
|  | ||||
|     override fun popularMangaParse(response: Response) = genericMangaParse(response) | ||||
|     override fun searchMangaParse(response: Response) = genericMangaParse(response) | ||||
|     override fun latestUpdatesParse(response: Response) = genericMangaParse(response) | ||||
|  | ||||
|     fun exGet(url: String, page: Int? = null) | ||||
|     fun exGet(url: String, page: Int? = null, additionalHeaders: Headers? = null, cache: Boolean = true) | ||||
|         = GET(page?.let { | ||||
|             addParam(url, "page", Integer.toString(page - 1)) | ||||
|         } ?: url, headers) | ||||
|         } ?: url, additionalHeaders?.let { | ||||
|             val headers = headers.newBuilder() | ||||
|         it.toMultimap().forEach { t, u -> | ||||
|             u.forEach { | ||||
|                 headers.add(t, it) | ||||
|             } | ||||
|         } | ||||
|         headers.build() | ||||
|     } ?: headers).let { | ||||
|         if(!cache) | ||||
|             it.newBuilder().cacheControl(CacheControl.FORCE_NETWORK).build() | ||||
|         else | ||||
|             it | ||||
|     }!! | ||||
|  | ||||
|     /** | ||||
|      * Parse gallery page to metadata model | ||||
| @@ -266,6 +287,37 @@ class EHentai(override val id: Long, | ||||
|         throw UnsupportedOperationException("Unused method was called somehow!") | ||||
|     } | ||||
|  | ||||
|     //Too lazy to write return type | ||||
|     fun fetchFavorites() = { | ||||
|         //Used to get "s" cookie | ||||
|         val favoriteUrl = "$baseUrl/favorites.php" | ||||
|         val result = mutableListOf<ParsedManga>() | ||||
|         var page = 1 | ||||
|  | ||||
|         var favNames: List<String>? = null | ||||
|  | ||||
|         do { | ||||
|             val response2 = client.newCall(exGet(favoriteUrl, | ||||
|                     page = page, | ||||
|                     cache = false)).execute() | ||||
|             val doc = response2.asJsoup() | ||||
|  | ||||
|             //Parse favorites | ||||
|             val parsed = extendedGenericMangaParse(doc) | ||||
|             result += parsed.first | ||||
|  | ||||
|             //Parse fav names | ||||
|             if (favNames == null) | ||||
|                 favNames = doc.getElementsByClass("nosel").first().children().filter { | ||||
|                     it.children().size >= 3 | ||||
|                 }.map { it.child(2).text() }.filterNotNull() | ||||
|  | ||||
|             //Next page | ||||
|             page++ | ||||
|         } while (parsed.second) | ||||
|         Pair(result as List<ParsedManga>, favNames!!) | ||||
|     }() | ||||
|  | ||||
|     val cookiesHeader by lazy { | ||||
|         val cookies: MutableMap<String, String> = mutableMapOf() | ||||
|         if(prefs.enableExhentai().getOrDefault()) { | ||||
| @@ -403,5 +455,20 @@ class EHentai(override val id: Long, | ||||
|     companion object { | ||||
|         val QUERY_PREFIX = "?f_apply=Apply+Filter" | ||||
|         val TR_SUFFIX = "TR" | ||||
|  | ||||
|         fun getCookies(cookies: String): Map<String, String>? { | ||||
|             val foundCookies = HashMap<String, String>() | ||||
|             for (cookie in cookies.split(";".toRegex()).dropLastWhile(String::isEmpty).toTypedArray()) { | ||||
|                 val splitCookie = cookie.split("=".toRegex()).dropLastWhile(String::isEmpty).toTypedArray() | ||||
|                 if (splitCookie.size < 2) { | ||||
|                     return null | ||||
|                 } | ||||
|                 val trimmedKey = splitCookie[0].trim { it <= ' ' } | ||||
|                 if (!foundCookies.containsKey(trimmedKey)) { | ||||
|                     foundCookies.put(trimmedKey, splitCookie[1].trim { it <= ' ' }) | ||||
|                 } | ||||
|             } | ||||
|             return foundCookies | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -27,6 +27,7 @@ import eu.kanade.tachiyomi.ui.main.MainActivity | ||||
| import eu.kanade.tachiyomi.util.inflate | ||||
| import eu.kanade.tachiyomi.util.toast | ||||
| import eu.kanade.tachiyomi.widget.DialogCheckboxView | ||||
| import exh.FavoritesSyncHelper | ||||
| import kotlinx.android.synthetic.main.activity_main.* | ||||
| import kotlinx.android.synthetic.main.fragment_library.* | ||||
| import nucleus.factory.RequiresPresenter | ||||
| @@ -267,6 +268,11 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback | ||||
|                 val intent = CategoryActivity.newIntent(activity) | ||||
|                 startActivity(intent) | ||||
|             } | ||||
|             R.id.action_sync -> { | ||||
|                 FavoritesSyncHelper(this.activity).guiSyncFavorites({ | ||||
|                     //Do we even need stuff in here? | ||||
|                 }) | ||||
|             } | ||||
|             else -> return super.onOptionsItemSelected(item) | ||||
|         } | ||||
|  | ||||
|   | ||||
							
								
								
									
										135
									
								
								app/src/main/java/exh/FavoritesSyncHelper.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								app/src/main/java/exh/FavoritesSyncHelper.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,135 @@ | ||||
| package exh | ||||
|  | ||||
| import android.app.Activity | ||||
| import android.support.v7.app.AlertDialog | ||||
| import com.afollestad.materialdialogs.MaterialDialog | ||||
| import eu.kanade.tachiyomi.data.database.DatabaseHelper | ||||
| import eu.kanade.tachiyomi.data.database.models.Category | ||||
| import eu.kanade.tachiyomi.data.database.models.Manga | ||||
| import eu.kanade.tachiyomi.data.database.models.MangaCategory | ||||
| import eu.kanade.tachiyomi.data.preference.PreferencesHelper | ||||
| import eu.kanade.tachiyomi.data.preference.getOrDefault | ||||
| import eu.kanade.tachiyomi.source.SourceManager | ||||
| import eu.kanade.tachiyomi.source.online.all.EHentai | ||||
| import eu.kanade.tachiyomi.util.syncChaptersWithSource | ||||
| import timber.log.Timber | ||||
| import uy.kohesive.injekt.injectLazy | ||||
| import kotlin.concurrent.thread | ||||
|  | ||||
| class FavoritesSyncHelper(val activity: Activity) { | ||||
|  | ||||
|     val db: DatabaseHelper by injectLazy() | ||||
|  | ||||
|     val sourceManager: SourceManager by injectLazy() | ||||
|  | ||||
|     val prefs: PreferencesHelper by injectLazy() | ||||
|  | ||||
|     fun guiSyncFavorites(onComplete: () -> Unit) { | ||||
|         //ExHentai must be enabled/user must be logged in | ||||
|         if (!prefs.enableExhentai().getOrDefault()) { | ||||
|             AlertDialog.Builder(activity).setTitle("Error") | ||||
|                     .setMessage("You are not logged in! Please log in and try again!") | ||||
|                     .setPositiveButton("Ok") { dialog, _ -> dialog.dismiss() }.show() | ||||
|             return | ||||
|         } | ||||
|         val dialog = MaterialDialog.Builder(activity) | ||||
|                 .progress(true, 0) | ||||
|                 .title("Downloading favorites") | ||||
|                 .content("Please wait...") | ||||
|                 .cancelable(false) | ||||
|                 .show() | ||||
|         thread { | ||||
|             var error = false | ||||
|             try { | ||||
|                 syncFavorites() | ||||
|             } catch (e: Exception) { | ||||
|                 error = true | ||||
|                 Timber.e(e, "Could not sync favorites!") | ||||
|             } | ||||
|  | ||||
|             dialog.dismiss() | ||||
|  | ||||
|             activity.runOnUiThread { | ||||
|                 if (error) | ||||
|                     MaterialDialog.Builder(activity) | ||||
|                             .title("Error") | ||||
|                             .content("There was an error downloading your favorites, please try again later!") | ||||
|                             .positiveText("Ok") | ||||
|                             .show() | ||||
|                 onComplete() | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun syncFavorites() { | ||||
|         val onlineSources = sourceManager.getOnlineSources() | ||||
|         var ehSource: EHentai? = null | ||||
|         var exSource: EHentai? = null | ||||
|         onlineSources.forEach { | ||||
|             if(it.id == EH_SOURCE_ID) | ||||
|                 ehSource = it as EHentai | ||||
|             else if(it.id == EXH_SOURCE_ID) | ||||
|                 exSource = it as EHentai | ||||
|         } | ||||
|  | ||||
|         (exSource ?: ehSource)?.let { source -> | ||||
|             val favResponse = source.fetchFavorites() | ||||
|             val ourCategories = ArrayList<Category>(db.getCategories().executeAsBlocking()) | ||||
|             val ourMangas = ArrayList<Manga>(db.getMangas().executeAsBlocking()) | ||||
|             //Add required categories (categories do not sync upwards) | ||||
|             favResponse.second.filter { theirCategory -> | ||||
|                 ourCategories.find { | ||||
|                     it.name.endsWith(theirCategory) | ||||
|                 } == null | ||||
|             }.map { | ||||
|                 Category.create(it) | ||||
|             }.let { | ||||
|                 db.inTransaction { | ||||
|                     //Insert new categories | ||||
|                     db.insertCategories(it).executeAsBlocking().results().entries.filter { | ||||
|                         it.value.wasInserted() | ||||
|                     }.forEach { it.key.id = it.value.insertedId()!!.toInt() } | ||||
|  | ||||
|                     val categoryMap = (it + ourCategories).associateBy { it.name } | ||||
|  | ||||
|                     //Insert new mangas | ||||
|                     val mangaToInsert = java.util.ArrayList<Manga>() | ||||
|                     favResponse.first.map { | ||||
|                         val category = categoryMap[it.fav]!! | ||||
|                         var manga = it.manga | ||||
|                         val alreadyHaveManga = ourMangas.find { | ||||
|                             it.url == manga.url | ||||
|                         }?.apply { | ||||
|                             manga = this | ||||
|                         } != null | ||||
|                         if (!alreadyHaveManga) { | ||||
|                             ourMangas.add(manga) | ||||
|                             mangaToInsert.add(manga) | ||||
|                         } | ||||
|                         manga.favorite = true | ||||
|                         Pair(manga, category) | ||||
|                     }.apply { | ||||
|                         //Insert mangas | ||||
|                         db.insertMangas(mangaToInsert).executeAsBlocking().results().entries.filter { | ||||
|                             it.value.wasInserted() | ||||
|                         }.forEach { manga -> | ||||
|                             manga.key.id = manga.value.insertedId() | ||||
|                             try { | ||||
|                                 source.fetchChapterList(manga.key).map { | ||||
|                                     syncChaptersWithSource(db, it, manga.key, source) | ||||
|                                 }.toBlocking().first() | ||||
|                             } catch (e: Exception) { | ||||
|                                 Timber.w(e, "Failed to update chapters for gallery: ${manga.key.title}!") | ||||
|                             } | ||||
|                         } | ||||
|  | ||||
|                         //Set categories | ||||
|                         val categories = map { MangaCategory.create(it.first, it.second) } | ||||
|                         val mangas = map { it.first } | ||||
|                         db.setMangaCategories(categories, mangas) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -20,11 +20,12 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper; | ||||
| import eu.kanade.tachiyomi.data.database.models.Category; | ||||
| import eu.kanade.tachiyomi.data.database.models.Manga; | ||||
| import eu.kanade.tachiyomi.data.database.models.MangaCategory; | ||||
| import eu.kanade.tachiyomi.source.online.all.EHentai; | ||||
| import kotlin.Pair; | ||||
| //import eu.kanade.tachiyomi.data.source.online.english.EHentai; | ||||
|  | ||||
| public class FavoritesSyncManager { | ||||
|     /* | ||||
|     Context context; | ||||
|     /*Context context; | ||||
|     DatabaseHelper db; | ||||
|  | ||||
|     public FavoritesSyncManager(Context context, DatabaseHelper db) { | ||||
| @@ -72,10 +73,10 @@ public class FavoritesSyncManager { | ||||
|                 mainLooper.post(onComplete); | ||||
|             } | ||||
|         }).start(); | ||||
|     } | ||||
|  | ||||
|     }*/ | ||||
| /* | ||||
|     public void syncFavorites() throws IOException { | ||||
|         EHentai.FavoritesResponse favResponse = EHentai.fetchFavorites(context); | ||||
|         Pair favResponse = EHentai.fetchFavorites(context); | ||||
|         Map<String, List<Manga>> favorites = favResponse.favs; | ||||
|         List<Category> ourCategories = new ArrayList<>(db.getCategories().executeAsBlocking()); | ||||
|         List<Manga> ourMangas = new ArrayList<>(db.getMangas().executeAsBlocking()); | ||||
| @@ -136,7 +137,7 @@ public class FavoritesSyncManager { | ||||
|         for(Map.Entry<Manga, Category> entry : mangaToSetCategories.entrySet()) { | ||||
|             db.setMangaCategories(Collections.singletonList(MangaCategory.Companion.create(entry.getKey(), entry.getValue())), | ||||
|                     Collections.singletonList(entry.getKey())); | ||||
|         } | ||||
|         }*/ | ||||
|         //Determines what | ||||
|         /*Map<Integer, List<Manga>> toUpload = new HashMap<>(); | ||||
|         for (Manga manga : ourMangas) { | ||||
|   | ||||
| @@ -7,11 +7,18 @@ import android.view.MenuItem | ||||
| import android.webkit.CookieManager | ||||
| import android.webkit.WebView | ||||
| import android.webkit.WebViewClient | ||||
| import com.afollestad.materialdialogs.MaterialDialog | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.data.preference.PreferencesHelper | ||||
| import eu.kanade.tachiyomi.source.SourceManager | ||||
| import eu.kanade.tachiyomi.source.online.all.EHentai | ||||
| import eu.kanade.tachiyomi.ui.base.activity.BaseActivity | ||||
| import exh.EXH_SOURCE_ID | ||||
| import kotlinx.android.synthetic.main.eh_activity_login.* | ||||
| import kotlinx.android.synthetic.main.toolbar.* | ||||
| import rx.Observable | ||||
| import rx.android.schedulers.AndroidSchedulers | ||||
| import rx.schedulers.Schedulers | ||||
| import timber.log.Timber | ||||
| import uy.kohesive.injekt.injectLazy | ||||
| import java.net.HttpCookie | ||||
| @@ -24,6 +31,8 @@ class LoginActivity : BaseActivity() { | ||||
|  | ||||
|     val preferenceManager: PreferencesHelper by injectLazy() | ||||
|  | ||||
|     val sourceManager: SourceManager by injectLazy() | ||||
|  | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         setAppTheme() | ||||
|         super.onCreate(savedInstanceState) | ||||
| @@ -73,13 +82,40 @@ class LoginActivity : BaseActivity() { | ||||
|                     //At ExHentai, check that everything worked out... | ||||
|                     if(applyExHentaiCookies(url)) { | ||||
|                         preferenceManager.enableExhentai().set(true) | ||||
|                         onBackPressed() | ||||
|                         finishLogin() | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     fun finishLogin() { | ||||
|         val progressDialog = MaterialDialog.Builder(this) | ||||
|                 .title("Finalizing login") | ||||
|                 .progress(true, 0) | ||||
|                 .content("Please wait...") | ||||
|                 .cancelable(false) | ||||
|                 .show() | ||||
|  | ||||
|         val eh = sourceManager | ||||
|                 .getOnlineSources() | ||||
|                 .find { it.id == EXH_SOURCE_ID } as EHentai | ||||
|         Observable.fromCallable { | ||||
|             //I honestly have no idea why we need to call this twice, but it works, so whatever | ||||
|             try { | ||||
|                 eh.fetchFavorites() | ||||
|             } catch(ignored: Exception) {} | ||||
|             try { | ||||
|                 eh.fetchFavorites() | ||||
|             } catch(ignored: Exception) {} | ||||
|         }.subscribeOn(Schedulers.io()) | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .subscribe { | ||||
|                     progressDialog.dismiss() | ||||
|                     onBackPressed() | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Check if we are logged in | ||||
|      */ | ||||
| @@ -127,7 +163,7 @@ class LoginActivity : BaseActivity() { | ||||
|  | ||||
|     fun getCookies(url: String): List<HttpCookie>? | ||||
|             = CookieManager.getInstance().getCookie(url)?.let { | ||||
|         it.split("; ").flatMap {  | ||||
|         it.split("; ").flatMap { | ||||
|             HttpCookie.parse(it) | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -0,0 +1,9 @@ | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|         android:width="24dp" | ||||
|         android:height="24dp" | ||||
|         android:viewportWidth="24.0" | ||||
|         android:viewportHeight="24.0"> | ||||
|     <path | ||||
|         android:fillColor="#ffffff" | ||||
|         android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM17,13l-5,5 -5,-5h3V9h4v4h3z"/> | ||||
| </vector> | ||||
| @@ -22,9 +22,14 @@ | ||||
|         android:title="@string/action_update_library" | ||||
|         app:showAsAction="ifRoom"/> | ||||
|  | ||||
|     <item | ||||
|         android:id="@+id/action_sync" | ||||
|         android:icon="@drawable/ic_cloud_download_white_24dp" | ||||
|         android:title="Download favorites" | ||||
|         app:showAsAction="ifRoom"/> | ||||
|  | ||||
|     <item | ||||
|         android:id="@+id/action_edit_categories" | ||||
|         android:title="@string/action_edit_categories" | ||||
|         app:showAsAction="never"/> | ||||
|  | ||||
| </menu> | ||||
|   | ||||
| @@ -31,11 +31,10 @@ | ||||
|             android:defaultValue="false" /> | ||||
|  | ||||
|         <SwitchPreference | ||||
|             android:dependency="enable_exhentai" | ||||
|             android:defaultValue="true" | ||||
|             android:key="secure_exh" | ||||
|             android:title="Secure ExHentai" | ||||
|             android:summary="Use the HTTPS version of ExHentai. Uncheck if ExHentai is not working" /> | ||||
|             android:title="Secure ExHentai/E-Hentai" | ||||
|             android:summary="Use the HTTPS version of ExHentai/E-Hentai." /> | ||||
|  | ||||
|         <ListPreference | ||||
|             android:defaultValue="auto" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user