Optimizations/Upgrades from the old code
This commit is contained in:
@@ -1,32 +1,107 @@
|
|||||||
login_errors_failed_login=Login failed. Something went wrong
|
# ================ #
|
||||||
login_errors_header_token_empty="Error: The JSON Web Token is empty.\nTry opening the extension first."
|
# Authentication #
|
||||||
login_errors_invalid_url=Invalid URL:
|
# ================ #
|
||||||
login_errors_parse_tokendto=There was an error parsing the auth token
|
login_errors_failed_login=Authentication failed.\nPlease check your credentials
|
||||||
pref_customsource_title=Displayed name for source
|
login_errors_header_token_empty=Authorization token missing\nPlease open the extension first to generate a token
|
||||||
pref_group_tags_title=Group Tags (fork must support grouping)
|
login_errors_invalid_url=Invalid server URL:
|
||||||
pref_group_tags_summary_on=Will prefix tags with their type
|
login_errors_parse_tokendto=Failed to process authentication token
|
||||||
pref_group_tags_summary_off=List all tags together
|
http_errors_401=Authentication expired\nPlease log in again
|
||||||
pref_last_volume_cover_title=Keep cover updated
|
http_errors_500=Server error occurred
|
||||||
pref_last_volume_cover_summary_off=Keep current cover
|
|
||||||
|
# ============== #
|
||||||
|
# Preferences #
|
||||||
|
# ============== #
|
||||||
|
pref_customsource_title=Source Display Name
|
||||||
|
pref_edit_customsource_summary=Customize how this Kavita instance appears in your library\nExample: "Home Server" or "Cloud Instance"
|
||||||
|
|
||||||
|
pref_group_tags_title=Tag Grouping (fork must support grouping)
|
||||||
|
pref_group_tags_summary_on=Show tags with category prefixes
|
||||||
|
pref_group_tags_summary_off=Display tags as flat list
|
||||||
|
|
||||||
|
pref_last_volume_cover_title=Keep Cover Updated
|
||||||
|
pref_last_volume_cover_summary_off=Keep series cover
|
||||||
pref_last_volume_cover_summary_on=Always show current Volume/Issue cover on refresh
|
pref_last_volume_cover_summary_on=Always show current Volume/Issue cover on refresh
|
||||||
pref_show_epub_title=Show EPUB (EPUB can't be read in Mihon)
|
|
||||||
pref_show_epub_summary_off=Hide EPUB titles in browsing
|
pref_show_epub_title=EPUB Visibility
|
||||||
pref_show_epub_summary_on=Show EPUB titles in browsing
|
pref_show_epub_summary_off=Hide EPUB entries
|
||||||
|
pref_show_epub_summary_on=Show EPUB entries (unreadable in Mihon)
|
||||||
|
|
||||||
pref_rd_date_title=Reading List: Display Release Date
|
pref_rd_date_title=Reading List: Display Release Date
|
||||||
pref_rd_date_summary_off=Do not display the release date
|
pref_rd_date_summary_off=Hide publication dates
|
||||||
pref_rd_date_summary_on=Display the release date | !! Once you toggle it on, if you toggle it off again, it will stays as N/A !!
|
pref_rd_date_summary_on=Show publication dates\nNote: Once you toggle it on, if you toggle it off again, it will stays as N/A.
|
||||||
pref_reset_title=Reset All Settings
|
|
||||||
pref_reset_summary=Clear all preferences for this Kavita instance (Only affect the selected instance)
|
pref_reset_title=Reset Settings
|
||||||
pref_edit_customsource_summary=Here you can change this source name.\nYou can write a descriptive name to identify this OPDS URL.
|
pref_reset_summary=Clear all preferences for this Kavita instance
|
||||||
pref_filters_summary=Show these filters in the filter list
|
|
||||||
pref_filters_title=Default filters shown
|
pref_filters_title=Default Filters
|
||||||
pref_opds_badformed_url=Incorrect OPDS address. Please copy it from User settings \u2192 3rd party apps \u2192 OPDS URL
|
pref_filters_summary=Select which filters appear by default
|
||||||
pref_opds_duplicated_source_url=The URL is configured in a different source ->
|
|
||||||
pref_opds_must_setup_address=You must set up the address to communicate with Kavita
|
# ============== #
|
||||||
pref_opds_summary=The OPDS URL copied from User Settings. This should include address and end with the API key.
|
# OPDS Setup #
|
||||||
restartapp_settings=Restart Mihon to apply new setting.
|
# ============== #
|
||||||
version_exceptions_chapters_parse=Unhandled exception parsing chapters. Send your logs to the Kavita devs.
|
pref_opds_must_setup_address=OPDS URL required for connection
|
||||||
check_version=Ensure you have the newest version of the extension and Kavita. (0.7.8 or newer.)\nIf the issue persists, report it to the Kavita developers with the accompanying logs.
|
pref_opds_badformed_url=Invalid OPDS address\nCopy from: User Settings > API Key / OPDS > OPDS URL (settings#clients)
|
||||||
version_exceptions_smart_filter=Could not decode SmartFilter. Ensure you are using Kavita version 0.7.11 or later.
|
pref_opds_duplicated_source_url=URL already used in another source:
|
||||||
http_errors_500=Something went wrong
|
pref_opds_summary=Complete OPDS URL including API key\nFormat: http://address:port/opds?apiKey=XXX
|
||||||
http_errors_401=There was an error logging in. Try again or reload the app
|
|
||||||
|
# ============== #
|
||||||
|
# System Messages #
|
||||||
|
# ============== #
|
||||||
|
restartapp_settings=Restart Mihon to apply changes
|
||||||
|
check_version=Requires Kavita v0.8.7+\nIf issues persist, contact support with logs
|
||||||
|
version_exceptions_chapters_parse=Chapter parsing error\nPlease report with logs
|
||||||
|
version_exceptions_smart_filter=Smart Filter error\nRequires Kavita v0.7.11+
|
||||||
|
|
||||||
|
|
||||||
|
# ============== #
|
||||||
|
# Filters #
|
||||||
|
# ============== #
|
||||||
|
# Filter headers
|
||||||
|
filters_special=These filters can't be combined with other filters
|
||||||
|
filters_people=People Filters
|
||||||
|
filters_special_list=Special Lists
|
||||||
|
filters_smart_filters=Smart Filters
|
||||||
|
|
||||||
|
# @todo
|
||||||
|
# Basic filters
|
||||||
|
filters_user_rating=User Rating
|
||||||
|
filters_status=Status
|
||||||
|
filters_sort=Sort By
|
||||||
|
filters_release_year=Release Year
|
||||||
|
filters_genres=Genres
|
||||||
|
filters_tags=Tags
|
||||||
|
filters_age_ratings=Age Ratings
|
||||||
|
filters_collections=Collections
|
||||||
|
filters_languages=Languages
|
||||||
|
filters_libraries=Libraries
|
||||||
|
filters_pub_status=Publication Status
|
||||||
|
|
||||||
|
# People filters
|
||||||
|
filters_writers=Writers
|
||||||
|
filters_pencillers=Pencillers
|
||||||
|
filters_inkers=Inkers
|
||||||
|
filters_colorists=Colorists
|
||||||
|
filters_letterers=Letterers
|
||||||
|
filters_cover_artists=Cover Artists
|
||||||
|
filters_editors=Editors
|
||||||
|
filters_publishers=Publishers
|
||||||
|
filters_characters=Characters
|
||||||
|
filters_translators=Translators
|
||||||
|
|
||||||
|
# Status options
|
||||||
|
filters_status_any=Any
|
||||||
|
filters_status_unread=Unread
|
||||||
|
filters_status_in_progress=In Progress
|
||||||
|
filters_status_read=Read
|
||||||
|
|
||||||
|
# Sort options
|
||||||
|
filters_sort_title=Title
|
||||||
|
filters_sort_rating=Rating
|
||||||
|
filters_sort_date_added=Date Added
|
||||||
|
filters_sort_release_date=Release Date
|
||||||
|
filters_sort_read_progress=Read Progress
|
||||||
|
|
||||||
|
# Error messages
|
||||||
|
filters_error_loading=Failed to load filters
|
||||||
|
filters_empty_list=No filters available
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,107 @@
|
|||||||
version_exceptions_chapters_parse=Exception non trait\u00E9e durant l'analyse des chapitres. Envoyez les logs aux d\u00E9velopeurs de Kavita.
|
# ================ #
|
||||||
pref_customsource_title=Nom d'affichage pour la source
|
# Authentification #
|
||||||
version_exceptions_smart_filter=\u00C9chec du d\u00E9codage de SmartFilter. Assurez-vous que vous utilisez au moins Kavita version 0.7.11
|
# ================ #
|
||||||
pref_opds_summary=L'URL OPDS a \u00E9t\u00E9 copi\u00E9e \u00E0 partir des param\u00E8tres de l'utilisateur. Ceci devrait inclure l'adresse et la cl\u00E9 API.
|
login_errors_failed_login=Échec de l'authentification. Vérifiez vos identifiants
|
||||||
pref_filters_summary=Afficher ces filtres dans la liste des filtres
|
login_errors_header_token_empty=Token d'autorisation manquant\nOuvrez d'abord l'extension pour générer un token
|
||||||
check_version=Assurez-vous que vous avez l'extension et Kavita mises \u00E0 jour. (version Mini\u202F: 0.7.8)\nSi le probl\u00E8me persiste, signalez-le aux d\u00E9veloppeurs de Kavita en fournissant des journaux
|
login_errors_invalid_url=URL du serveur invalide :
|
||||||
pref_filters_title=Filtres par d\u00E9faut affich\u00E9s
|
login_errors_parse_tokendto=Échec du traitement du token d'authentification
|
||||||
pref_edit_customsource_summary=Ici vous pouvez changer ce nom source.\nVous pouvez \u00E9crire un nom descriptif pour identifier cette URL opds
|
http_errors_401=Authentification expirée. Veuillez vous reconnecter
|
||||||
pref_opds_badformed_url=L'adresse OPDS n'est pas correcte. Veuillez la copier \u00E0 partir des param\u00E8tres de l'utilisateur - > Applis tierces -> URL OPDS
|
http_errors_500=Erreur du serveur
|
||||||
login_errors_parse_tokendto=Il y a eu une erreur pendant l'analyse du jeton d'authentification
|
|
||||||
restartapp_settings=Red\u00E9marrez Mihon pour appliquer le nouveau r\u00E9glage.
|
# ============== #
|
||||||
pref_opds_duplicated_source_url=L'URL est configur\u00E9e dans une autre source ->
|
# Préférences #
|
||||||
pref_opds_must_setup_address=Vous devez configurer l'adresse pour communiquer avec Kavita
|
# ============== #
|
||||||
login_errors_failed_login=\u00C9chec de la connexion. Quelque chose s'est mal pass\u00E9
|
pref_customsource_title=Nom d'affichage de la source
|
||||||
http_errors_500=Quelque chose s'est mal pass\u00E9
|
pref_edit_customsource_summary=Personnalisez l'affichage de cette instance Kavita\nExemple : "Serveur Maison" ou "Instance Cloud"
|
||||||
login_errors_header_token_empty=\u00AB\u00A0Erreur\u202F: le token jwt est vide.\nEssayez d'abord d'ouvrir l'extension\u00A0\u00BB
|
|
||||||
login_errors_invalid_url=URL invalide\u202F:
|
pref_group_tags_title=Groupement des tags (fork doit supporter le groupement)
|
||||||
http_errors_401=Il y a eu une erreur. Essayez de nouveau ou relancez l'application
|
pref_group_tags_summary_on=Afficher les tags avec préfixes
|
||||||
|
pref_group_tags_summary_off=Afficher les tags en liste plate
|
||||||
|
|
||||||
|
pref_last_volume_cover_title=Garder la couverture à jour
|
||||||
|
pref_last_volume_cover_summary_off=Conserver la couverture de la série
|
||||||
|
pref_last_volume_cover_summary_on=Toujours afficher la dernière couverture
|
||||||
|
|
||||||
|
pref_show_epub_title=Visibilité EPUB
|
||||||
|
pref_show_epub_summary_off=Masquer les EPUB
|
||||||
|
pref_show_epub_summary_on=Afficher les EPUB (illisibles dans Mihon)
|
||||||
|
|
||||||
|
pref_rd_date_title=Listes de Lecture: Affichage date de publication
|
||||||
|
pref_rd_date_summary_off=Masquer les dates de publication
|
||||||
|
pref_rd_date_summary_on=Afficher les dates de publication\nNote : Désactiver affichera toujours "N/A" après activation
|
||||||
|
|
||||||
|
pref_reset_title=Réinitialisation
|
||||||
|
pref_reset_summary=Effacer toutes les préférences pour cette instance Kavita
|
||||||
|
|
||||||
|
pref_filters_title=Filtres par défaut
|
||||||
|
pref_filters_summary=Sélectionnez les filtres visibles par défaut
|
||||||
|
|
||||||
|
# ============== #
|
||||||
|
# Configuration OPDS #
|
||||||
|
# ============== #
|
||||||
|
pref_opds_must_setup_address=URL OPDS requise pour la connexion
|
||||||
|
pref_opds_badformed_url=URL OPDS invalide\nCopiez depuis : Paramètres > Clé API / OPDS > URL OPDS
|
||||||
|
pref_opds_duplicated_source_url=URL déjà utilisée dans une autre source :
|
||||||
|
pref_opds_summary=URL OPDS complète incluant la clé API\nFormat : http://adresse:port/opds?apiKey=XXX
|
||||||
|
|
||||||
|
# ============== #
|
||||||
|
# Messages système #
|
||||||
|
# ============== #
|
||||||
|
restartapp_settings=Redémarrez Mihon pour appliquer les changements
|
||||||
|
check_version=Nécessite Kavita v0.8.7+\nSi problème persiste, contactez le support avec les logs
|
||||||
|
version_exceptions_chapters_parse=Erreur d'analyse des chapitres\nMerci de rapporter avec les logs
|
||||||
|
version_exceptions_smart_filter=Erreur Smart Filter\nNécessite Kavita v0.7.11+
|
||||||
|
|
||||||
|
|
||||||
|
# ============== #
|
||||||
|
# Filtres #
|
||||||
|
# ============== #
|
||||||
|
# Filter headers
|
||||||
|
filters_special=Ces filtres ne peuvent pas être combinés avec d'autres filtres
|
||||||
|
filters_people=Filtres Personnalités
|
||||||
|
|
||||||
|
# Special filters
|
||||||
|
filters_special_list=Listes Spéciales
|
||||||
|
filters_smart_filters=Filtres Intelligents
|
||||||
|
|
||||||
|
# Basic filters
|
||||||
|
filters_user_rating=Note Utilisateur
|
||||||
|
filters_status=Statut
|
||||||
|
filters_sort=Trier Par
|
||||||
|
filters_release_year=Année de Sortie
|
||||||
|
filters_genres=Genres
|
||||||
|
filters_tags=Étiquettes
|
||||||
|
filters_age_ratings=Classifications d'Âge
|
||||||
|
filters_collections=Collections
|
||||||
|
filters_languages=Langues
|
||||||
|
filters_libraries=Bibliothèques
|
||||||
|
filters_pub_status=Statut de Publication
|
||||||
|
|
||||||
|
# People filters
|
||||||
|
filters_writers=Auteur
|
||||||
|
filters_pencillers=Dessinateurs
|
||||||
|
filters_inkers=Encreurs
|
||||||
|
filters_colorists=Coloristes
|
||||||
|
filters_letterers=Lettrists
|
||||||
|
filters_cover_artists=Artistes de Couverture
|
||||||
|
filters_editors=Éditeurs
|
||||||
|
filters_publishers=Éditeurs
|
||||||
|
filters_characters=Personnalités
|
||||||
|
filters_translators=Traducteurs
|
||||||
|
|
||||||
|
# Status options
|
||||||
|
filters_status_any=Tous
|
||||||
|
filters_status_unread=Non Lu
|
||||||
|
filters_status_in_progress=En Cours
|
||||||
|
filters_status_read=Lu
|
||||||
|
|
||||||
|
# Sort options
|
||||||
|
filters_sort_title=Titre
|
||||||
|
filters_sort_rating=Note
|
||||||
|
filters_sort_date_added=Date d'Ajout
|
||||||
|
filters_sort_release_date=Date de Sortie
|
||||||
|
filters_sort_read_progress=Progrès de Lecture
|
||||||
|
|
||||||
|
# Error messages
|
||||||
|
filters_error_loading=Échec du chargement des filtres
|
||||||
|
filters_empty_list=Aucun filtre disponible
|
||||||
|
|||||||
@@ -3,25 +3,6 @@ package eu.kanade.tachiyomi.extension.all.kavita
|
|||||||
import eu.kanade.tachiyomi.extension.all.kavita.KavitaConstants.noSmartFilterSelected
|
import eu.kanade.tachiyomi.extension.all.kavita.KavitaConstants.noSmartFilterSelected
|
||||||
import eu.kanade.tachiyomi.source.model.Filter
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
|
|
||||||
class UserRating : Filter.Select<Int>(
|
|
||||||
"Minimum Rating",
|
|
||||||
arrayOf(
|
|
||||||
"Any",
|
|
||||||
"1 star",
|
|
||||||
"2 stars",
|
|
||||||
"3 stars",
|
|
||||||
"4 stars",
|
|
||||||
"5 stars",
|
|
||||||
).mapIndexed { index, _ -> index }.toTypedArray(),
|
|
||||||
) {
|
|
||||||
override fun toString(): String {
|
|
||||||
return when (state) {
|
|
||||||
0 -> "Any"
|
|
||||||
else -> "$state star${if (state > 1) "s" else ""}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SmartFiltersFilter(smartFilters: Array<String>) :
|
class SmartFiltersFilter(smartFilters: Array<String>) :
|
||||||
Filter.Select<String>("Smart Filters", arrayOf(noSmartFilterSelected) + smartFilters)
|
Filter.Select<String>("Smart Filters", arrayOf(noSmartFilterSelected) + smartFilters)
|
||||||
class SortFilter(sortables: Array<String>) : Filter.Sort("Sort by", sortables, Selection(0, true))
|
class SortFilter(sortables: Array<String>) : Filter.Sort("Sort by", sortables, Selection(0, true))
|
||||||
@@ -38,8 +19,8 @@ val sortableList = listOf(
|
|||||||
Pair("Random", 9),
|
Pair("Random", 9),
|
||||||
)
|
)
|
||||||
|
|
||||||
class SpecialListFilter : Filter.Select<String>(
|
class SpecialListFilter(name: String = "Special Lists") : Filter.Select<String>(
|
||||||
"Special Lists",
|
name,
|
||||||
arrayOf("None", "Want to Read", "Reading Lists"),
|
arrayOf("None", "Want to Read", "Reading Lists"),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -47,9 +28,11 @@ class StatusFilter : Filter.Select<String>(
|
|||||||
"Status",
|
"Status",
|
||||||
arrayOf("Any", "Unread", "In Progress", "Read"),
|
arrayOf("Any", "Unread", "In Progress", "Read"),
|
||||||
)
|
)
|
||||||
|
class StatusSeparator :
|
||||||
|
Filter.Separator()
|
||||||
|
|
||||||
class StatusFilterGroup : Filter.Group<StatusFilter>(
|
class StatusFilterGroup(name: String = "Status") : Filter.Group<StatusFilter>(
|
||||||
"Status",
|
name,
|
||||||
listOf(StatusFilter()),
|
listOf(StatusFilter()),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -64,7 +47,8 @@ class GenreFilterGroup(genres: List<GenreFilter>) :
|
|||||||
Filter.Group<GenreFilter>("Genres", genres)
|
Filter.Group<GenreFilter>("Genres", genres)
|
||||||
|
|
||||||
class TagFilter(name: String) : Filter.TriState(name)
|
class TagFilter(name: String) : Filter.TriState(name)
|
||||||
class TagFilterGroup(tags: List<TagFilter>) : Filter.Group<TagFilter>("Tags", tags)
|
class TagFilterGroup(tags: List<TagFilter>) :
|
||||||
|
Filter.Group<TagFilter>("Tags", tags)
|
||||||
|
|
||||||
class AgeRatingFilter(name: String) : Filter.TriState(name)
|
class AgeRatingFilter(name: String) : Filter.TriState(name)
|
||||||
class AgeRatingFilterGroup(ageRatings: List<AgeRatingFilter>) :
|
class AgeRatingFilterGroup(ageRatings: List<AgeRatingFilter>) :
|
||||||
@@ -90,8 +74,29 @@ class PubStatusFilter(name: String) : Filter.CheckBox(name, false)
|
|||||||
class PubStatusFilterGroup(status: List<PubStatusFilter>) :
|
class PubStatusFilterGroup(status: List<PubStatusFilter>) :
|
||||||
Filter.Group<PubStatusFilter>("Publication Status", status)
|
Filter.Group<PubStatusFilter>("Publication Status", status)
|
||||||
|
|
||||||
|
class UserRating : Filter.Select<Int>(
|
||||||
|
"Minimum Rating",
|
||||||
|
arrayOf(
|
||||||
|
"Any",
|
||||||
|
"1 star",
|
||||||
|
"2 stars",
|
||||||
|
"3 stars",
|
||||||
|
"4 stars",
|
||||||
|
"5 stars",
|
||||||
|
).mapIndexed { index, _ -> index }.toTypedArray(),
|
||||||
|
) {
|
||||||
|
override fun toString(): String {
|
||||||
|
return when (state) {
|
||||||
|
0 -> "Any"
|
||||||
|
else -> "$state star${if (state > 1) "s" else ""}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class UserRatingSeparator :
|
||||||
|
Filter.Separator()
|
||||||
|
|
||||||
class PeopleHeaderFilter(name: String) :
|
class PeopleHeaderFilter(name: String) :
|
||||||
Filter.Header(name)
|
Filter.Header(name.toString())
|
||||||
class PeopleSeparatorFilter :
|
class PeopleSeparatorFilter :
|
||||||
Filter.Separator()
|
Filter.Separator()
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ import eu.kanade.tachiyomi.extension.all.kavita.dto.SortOptions
|
|||||||
import eu.kanade.tachiyomi.extension.all.kavita.dto.VolumeDto
|
import eu.kanade.tachiyomi.extension.all.kavita.dto.VolumeDto
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.POST
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
import eu.kanade.tachiyomi.source.UnmeteredSource
|
import eu.kanade.tachiyomi.source.UnmeteredSource
|
||||||
import eu.kanade.tachiyomi.source.model.Filter
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
@@ -55,6 +54,14 @@ import eu.kanade.tachiyomi.source.model.Page
|
|||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.awaitAll
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
@@ -68,16 +75,11 @@ import okhttp3.OkHttpClient
|
|||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import rx.Observable
|
|
||||||
import rx.Single
|
|
||||||
import rx.schedulers.Schedulers
|
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.net.ConnectException
|
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSource, HttpSource() {
|
class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSource, HttpSource() {
|
||||||
private val helper = KavitaHelper()
|
private val helper = KavitaHelper()
|
||||||
@@ -193,65 +195,40 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
* Custom implementation for fetch popular, latest and search
|
* Custom implementation for fetch popular, latest and search
|
||||||
* Handles and logs errors to provide a more detailed exception to the users.
|
* Handles and logs errors to provide a more detailed exception to the users.
|
||||||
*/
|
*/
|
||||||
private fun fetch(request: Request): Observable<MangasPage> {
|
private suspend fun fetch(request: Request): MangasPage = withContext(Dispatchers.IO) {
|
||||||
return client.newCall(request)
|
|
||||||
.asObservableSuccess()
|
|
||||||
.onErrorResumeNext { throwable ->
|
|
||||||
val field = throwable.javaClass.getDeclaredField("code")
|
|
||||||
field.isAccessible = true
|
|
||||||
try {
|
try {
|
||||||
var code = field.get(throwable)
|
val response = client.newCall(request).execute()
|
||||||
Log.e(LOG_TAG, "Error fetching manga: ${throwable.message}", throwable)
|
if (!response.isSuccessful) {
|
||||||
if (code as Int !in intArrayOf(401, 201, 500)) {
|
val code = response.code
|
||||||
code = 500
|
throw IOException("Http Error: $code\n ${helper.intl["http_errors_$code"]}\n${helper.intl["check_version"]}")
|
||||||
}
|
}
|
||||||
return@onErrorResumeNext Observable.error(IOException("Http Error: $code\n ${helper.intl["http_errors_$code"]}\n${helper.intl["check_version"]}"))
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(LOG_TAG, e.toString(), e)
|
|
||||||
return@onErrorResumeNext Observable.error(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.map { response ->
|
|
||||||
popularMangaParse(response)
|
popularMangaParse(response)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(LOG_TAG, "Error fetching manga", e)
|
||||||
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated(
|
override suspend fun getPopularManga(page: Int): MangasPage = withContext(Dispatchers.IO) {
|
||||||
"Use the non-RxJava API instead",
|
try {
|
||||||
replaceWith = ReplaceWith("getPopularManga(page)"),
|
val response = client.newCall(popularMangaRequest(page)).execute()
|
||||||
)
|
|
||||||
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
|
|
||||||
return client.newCall(popularMangaRequest(page))
|
|
||||||
.asObservableSuccess()
|
|
||||||
.map { response ->
|
|
||||||
if (!response.isSuccessful) {
|
if (!response.isSuccessful) {
|
||||||
throw IOException("HTTP error ${response.code}")
|
throw IOException("HTTP error ${response.code}")
|
||||||
}
|
}
|
||||||
popularMangaParse(response)
|
popularMangaParse(response)
|
||||||
}
|
} catch (e: Exception) {
|
||||||
.onErrorReturn { error ->
|
Log.e(LOG_TAG, "Error fetching popular manga", e)
|
||||||
Log.e(LOG_TAG, "Error fetching popular manga", error)
|
|
||||||
MangasPage(emptyList(), false)
|
MangasPage(emptyList(), false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated(
|
override suspend fun getLatestUpdates(page: Int): MangasPage = fetch(latestUpdatesRequest(page))
|
||||||
"Use the non-RxJava API instead",
|
|
||||||
replaceWith = ReplaceWith("getLatestUpdates(page)"),
|
|
||||||
)
|
|
||||||
override fun fetchLatestUpdates(page: Int) =
|
|
||||||
fetch(latestUpdatesRequest(page))
|
|
||||||
|
|
||||||
@Deprecated(
|
override suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage {
|
||||||
"Use the non-RxJava API instead",
|
|
||||||
replaceWith = ReplaceWith("getSearchManga(page, query, filters)"),
|
|
||||||
)
|
|
||||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
|
||||||
val specialListFilter = filters.find { it is SpecialListFilter } as? SpecialListFilter
|
val specialListFilter = filters.find { it is SpecialListFilter } as? SpecialListFilter
|
||||||
|
|
||||||
return when (specialListFilter?.state) {
|
return when (specialListFilter?.state) {
|
||||||
1 -> {
|
1 -> { // Want to Read selected
|
||||||
// Want to Read selected
|
|
||||||
val payload = """{
|
val payload = """{
|
||||||
"id": 0,
|
"id": 0,
|
||||||
"name": "",
|
"name": "",
|
||||||
@@ -268,14 +245,12 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
headersBuilder().build(),
|
headersBuilder().build(),
|
||||||
payload.toRequestBody(JSON_MEDIA_TYPE),
|
payload.toRequestBody(JSON_MEDIA_TYPE),
|
||||||
)
|
)
|
||||||
return client.newCall(request)
|
val response = client.newCall(request).execute()
|
||||||
.asObservableSuccess()
|
popularMangaParse(response)
|
||||||
.map { popularMangaParse(it) }
|
|
||||||
}
|
}
|
||||||
2 -> { // Reading Lists
|
2 -> { // Reading Lists
|
||||||
client.newCall(readingListRequest())
|
val response = client.newCall(readingListRequest()).execute()
|
||||||
.asObservableSuccess()
|
readingListParse(response)
|
||||||
.map { readingListParse(it) }
|
|
||||||
}
|
}
|
||||||
else -> { // Regular searches
|
else -> { // Regular searches
|
||||||
fetch(searchMangaRequest(page, query, filters))
|
fetch(searchMangaRequest(page, query, filters))
|
||||||
@@ -283,15 +258,11 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated(
|
override suspend fun getChapterList(manga: SManga): List<SChapter> {
|
||||||
"Use the non-RxJava API instead",
|
|
||||||
replaceWith = ReplaceWith("getChapterList(manga)"),
|
|
||||||
)
|
|
||||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
|
||||||
return if (manga.url.contains("/ReadingList/")) {
|
return if (manga.url.contains("/ReadingList/")) {
|
||||||
fetchReadingListItems(manga)
|
fetchReadingListItems(manga)
|
||||||
} else {
|
} else {
|
||||||
super.fetchChapterList(manga)
|
super.getChapterList(manga)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,7 +270,7 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
try {
|
try {
|
||||||
val result = response.parseAs<List<SeriesDto>>()
|
val result = response.parseAs<List<SeriesDto>>()
|
||||||
series = result
|
series = result
|
||||||
val mangaList = result.map { item -> helper.createSeriesDto(item, apiUrl, baseUrl, apiKey) }
|
val mangaList = result.map { item -> helper.createSeriesDto(item, apiUrl, baseUrl) }
|
||||||
return MangasPage(mangaList, helper.hasNextPage(response))
|
return MangasPage(mangaList, helper.hasNextPage(response))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(LOG_TAG, "Unhandled exception", e)
|
Log.e(LOG_TAG, "Unhandled exception", e)
|
||||||
@@ -431,7 +402,7 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
is Filter.Group<*> -> {
|
is Filter.Group<*> -> {
|
||||||
when (filter) {
|
when (filter) {
|
||||||
is StatusFilterGroup -> {
|
is StatusFilterGroup -> {
|
||||||
val statusFilter = filter.state.firstOrNull() as? StatusFilter
|
val statusFilter = filter.state.firstOrNull()
|
||||||
statusFilter?.state?.let { selectedIndex ->
|
statusFilter?.state?.let { selectedIndex ->
|
||||||
when (selectedIndex) {
|
when (selectedIndex) {
|
||||||
1 -> { // Unread (0%)
|
1 -> { // Unread (0%)
|
||||||
@@ -505,24 +476,32 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
}
|
}
|
||||||
|
|
||||||
is TagFilterGroup -> {
|
is TagFilterGroup -> {
|
||||||
filter.state.forEach { tagFilter ->
|
val included = filter.state
|
||||||
val tag = tagsListMeta.find { it.title == (tagFilter as Filter.TriState).name }
|
.filter { it.state == STATE_INCLUDE }
|
||||||
tag?.let {
|
.mapNotNull { tagFilter ->
|
||||||
when (tagFilter.state) {
|
tagsListMeta.find { it.title == tagFilter.name }?.id?.toString()
|
||||||
STATE_INCLUDE -> filterV2.addStatement(
|
}
|
||||||
|
val excluded = filter.state
|
||||||
|
.filter { it.state == STATE_EXCLUDE }
|
||||||
|
.mapNotNull { tagFilter ->
|
||||||
|
tagsListMeta.find { it.title == tagFilter.name }?.id?.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (included.isNotEmpty()) {
|
||||||
|
filterV2.addStatement(
|
||||||
FilterComparison.Contains,
|
FilterComparison.Contains,
|
||||||
FilterField.Tags,
|
FilterField.Tags,
|
||||||
tag.id.toString(),
|
included.joinToString(","),
|
||||||
)
|
)
|
||||||
STATE_EXCLUDE -> filterV2.addStatement(
|
}
|
||||||
|
if (excluded.isNotEmpty()) {
|
||||||
|
filterV2.addStatement(
|
||||||
FilterComparison.NotContains,
|
FilterComparison.NotContains,
|
||||||
FilterField.Tags,
|
FilterField.Tags,
|
||||||
tag.id.toString(),
|
excluded.joinToString(","),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
is AgeRatingFilterGroup -> {
|
is AgeRatingFilterGroup -> {
|
||||||
val included = filter.state
|
val included = filter.state
|
||||||
.filter { it.state == STATE_INCLUDE }
|
.filter { it.state == STATE_INCLUDE }
|
||||||
@@ -802,14 +781,12 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
"Use the non-RxJava API instead",
|
"Use the non-RxJava API instead",
|
||||||
replaceWith = ReplaceWith("getMangaDetails(manga)"),
|
replaceWith = ReplaceWith("getMangaDetails(manga)"),
|
||||||
)
|
)
|
||||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
override suspend fun getMangaDetails(manga: SManga): SManga {
|
||||||
val serieId = helper.getIdFromUrl(manga.url)
|
val serieId = helper.getIdFromUrl(manga.url)
|
||||||
|
|
||||||
return if (manga.url.contains("source=readinglist")) {
|
return if (manga.url.contains("source=readinglist")) {
|
||||||
val readingListId = manga.url.substringAfter("readingListId=").substringBefore("&").toIntOrNull()
|
val readingListId = manga.url.substringAfter("readingListId=").substringBefore("&").toIntOrNull()
|
||||||
val now = java.util.Calendar.getInstance()
|
val now = java.util.Calendar.getInstance()
|
||||||
|
|
||||||
Observable.fromCallable {
|
|
||||||
val readingList = cachedReadingLists.find { it.id == readingListId }
|
val readingList = cachedReadingLists.find { it.id == readingListId }
|
||||||
if (readingList != null) {
|
if (readingList != null) {
|
||||||
val groupTags = preferences.groupTags
|
val groupTags = preferences.groupTags
|
||||||
@@ -881,25 +858,13 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
this.status = status
|
this.status = status
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
manga.apply {
|
manga.apply { initialized = true }
|
||||||
initialized = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.onErrorReturn { error ->
|
|
||||||
Log.e(LOG_TAG, "Error fetching reading list details", error)
|
|
||||||
manga.apply {
|
|
||||||
description = helper.intl["error_loading_reading_list"]
|
|
||||||
initialized = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
client.newCall(GET("$apiUrl/series/metadata?seriesId=$serieId", headersBuilder().build()))
|
val response = client.newCall(GET("$apiUrl/series/metadata?seriesId=$serieId", headersBuilder().build())).execute()
|
||||||
.asObservableSuccess()
|
|
||||||
.map { response ->
|
|
||||||
mangaDetailsParse(response).apply { initialized = true }
|
mangaDetailsParse(response).apply { initialized = true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun mangaDetailsRequest(manga: SManga): Request {
|
override fun mangaDetailsRequest(manga: SManga): Request {
|
||||||
val serieId = helper.getIdFromUrl(manga.url)
|
val serieId = helper.getIdFromUrl(manga.url)
|
||||||
@@ -965,9 +930,9 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
.filterNot { it.equals(foundDemographic, ignoreCase = true) }
|
.filterNot { it.equals(foundDemographic, ignoreCase = true) }
|
||||||
.filterNot { tag -> filteredGenres.any { genre -> genre.equals(tag, ignoreCase = true) } }
|
.filterNot { tag -> filteredGenres.any { genre -> genre.equals(tag, ignoreCase = true) } }
|
||||||
|
|
||||||
val manga = helper.createSeriesDto(existingSeries, apiUrl, baseUrl, apiKey)
|
val manga = helper.createSeriesDto(existingSeries, apiUrl, baseUrl)
|
||||||
manga.title = existingSeries.name
|
manga.title = existingSeries.name
|
||||||
manga.url = "$baseUrl/library/${existingSeries.libraryId}/series/${result.seriesId}" // "$apiUrl/Series/${result.seriesId}"
|
manga.url = "$baseUrl/Series/${result.seriesId}" // "$baseUrl/library/${existingSeries.libraryId}/series/${result.seriesId}"
|
||||||
manga.artist = result.coverArtists.joinToString { it.name }
|
manga.artist = result.coverArtists.joinToString { it.name }
|
||||||
manga.description = listOfNotNull(
|
manga.description = listOfNotNull(
|
||||||
ratingLine.takeIf { it.isNotBlank() },
|
ratingLine.takeIf { it.isNotBlank() },
|
||||||
@@ -1092,7 +1057,7 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
.filterNot { it.equals(foundDemographic, ignoreCase = true) }
|
.filterNot { it.equals(foundDemographic, ignoreCase = true) }
|
||||||
.filterNot { tag -> filteredGenres.any { genre -> genre.equals(tag, ignoreCase = true) } }
|
.filterNot { tag -> filteredGenres.any { genre -> genre.equals(tag, ignoreCase = true) } }
|
||||||
|
|
||||||
url = "$baseUrl/library/${result.getLibraryId(serieDto)}/series/${result.seriesId}" // "$apiUrl/Series/${result.seriesId}"
|
url = "$baseUrl/Series/${result.seriesId}" // "$baseUrl/library/${result.getLibraryId(serieDto)}/series/${result.seriesId}"
|
||||||
artist = result.coverArtists.joinToString { it.name }
|
artist = result.coverArtists.joinToString { it.name }
|
||||||
description = listOfNotNull(
|
description = listOfNotNull(
|
||||||
ratingLine.takeIf { it.isNotBlank() },
|
ratingLine.takeIf { it.isNotBlank() },
|
||||||
@@ -1173,13 +1138,11 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchReadingListItems(manga: SManga): Observable<List<SChapter>> {
|
private fun fetchReadingListItems(manga: SManga): List<SChapter> {
|
||||||
val readingListId = manga.url.substringAfter("readingListId=").substringBefore("&")
|
val readingListId = manga.url.substringAfter("readingListId=").substringBefore("&")
|
||||||
val request = GET("$apiUrl/ReadingList/items?readingListId=$readingListId", headersBuilder().build())
|
val request = GET("$apiUrl/ReadingList/items?readingListId=$readingListId", headersBuilder().build())
|
||||||
|
val response = client.newCall(request).execute()
|
||||||
return client.newCall(request)
|
return parseReadingListItems(response)
|
||||||
.asObservableSuccess()
|
|
||||||
.map { parseReadingListItems(it) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseReadingListItems(response: Response): List<SChapter> {
|
private fun parseReadingListItems(response: Response): List<SChapter> {
|
||||||
@@ -1321,7 +1284,7 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
try {
|
try {
|
||||||
val seriesDto = client.newCall(GET("$apiUrl/Series/${item.seriesId}", headersBuilder().build()))
|
val seriesDto = client.newCall(GET("$apiUrl/Series/${item.seriesId}", headersBuilder().build()))
|
||||||
.execute().parseAs<SeriesDto>()
|
.execute().parseAs<SeriesDto>()
|
||||||
helper.createSeriesDto(seriesDto, apiUrl, baseUrl, apiKey)
|
helper.createSeriesDto(seriesDto, apiUrl, baseUrl)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(LOG_TAG, "Error fetching series for related reading list", e)
|
Log.e(LOG_TAG, "Error fetching series for related reading list", e)
|
||||||
null
|
null
|
||||||
@@ -1531,11 +1494,10 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
return GET("$apiUrl/$chapterId", headersBuilder().build())
|
return GET("$apiUrl/$chapterId", headersBuilder().build())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getPageList(chapter)"))
|
override suspend fun getPageList(chapter: SChapter): List<Page> = withContext(Dispatchers.IO) {
|
||||||
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
|
|
||||||
// Check if this is a reading list item (has readingListId in URL)
|
// Check if this is a reading list item (has readingListId in URL)
|
||||||
if (chapter.url.contains("readingListId=")) {
|
if (chapter.url.contains("readingListId=")) {
|
||||||
return Observable.fromCallable {
|
try {
|
||||||
// Extract seriesId from the chapter URL
|
// Extract seriesId from the chapter URL
|
||||||
val seriesId = chapter.url.substringAfter("seriesId=").substringBefore("&").toIntOrNull()
|
val seriesId = chapter.url.substringAfter("seriesId=").substringBefore("&").toIntOrNull()
|
||||||
val readingListId = chapter.url.substringAfter("readingListId=").substringBefore("&").toIntOrNull()
|
val readingListId = chapter.url.substringAfter("readingListId=").substringBefore("&").toIntOrNull()
|
||||||
@@ -1560,32 +1522,39 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
} ?: throw IOException("Item not found in reading list")
|
} ?: throw IOException("Item not found in reading list")
|
||||||
|
|
||||||
// Get item details based on type
|
// Get item details based on type
|
||||||
val chapterDetails = if (listItem.chapterId != null) {
|
val chapterDetails = when {
|
||||||
|
listItem.chapterId != null -> {
|
||||||
|
// Standard chapter
|
||||||
val request = GET("$apiUrl/Chapter?chapterId=${listItem.chapterId}", headersBuilder().build())
|
val request = GET("$apiUrl/Chapter?chapterId=${listItem.chapterId}", headersBuilder().build())
|
||||||
client.newCall(request).execute().parseAs<ChapterDto>()
|
client.newCall(request).execute().parseAs<ChapterDto>()
|
||||||
} else if (listItem.volumeId != null) {
|
}
|
||||||
// For volumes, get all chapters and let user choose
|
listItem.volumeId != null -> {
|
||||||
|
// Volume - get first chapter
|
||||||
val volumeRequest = GET("$apiUrl/Volume/${listItem.volumeId}", headersBuilder().build())
|
val volumeRequest = GET("$apiUrl/Volume/${listItem.volumeId}", headersBuilder().build())
|
||||||
val volume = client.newCall(volumeRequest).execute().parseAs<VolumeDto>()
|
val volume = client.newCall(volumeRequest).execute().parseAs<VolumeDto>()
|
||||||
volume.chapters.firstOrNull() ?: throw IOException(helper.intl["error_no_chapters_found"])
|
volume.chapters.firstOrNull()?.let {
|
||||||
} else {
|
val chapRequest = GET("$apiUrl/Chapter?chapterId=${it.id}", headersBuilder().build())
|
||||||
throw IOException(helper.intl["error_invalid_reading_list_item"])
|
client.newCall(chapRequest).execute().parseAs<ChapterDto>()
|
||||||
|
} ?: throw IOException(helper.intl["error_no_chapters_found"])
|
||||||
|
}
|
||||||
|
else -> throw IOException(helper.intl["error_invalid_reading_list_item"])
|
||||||
}
|
}
|
||||||
|
|
||||||
(0 until chapterDetails.pages).map { i ->
|
// Generate pages with consistent URL format
|
||||||
|
return@withContext (0 until chapterDetails.pages).map { i ->
|
||||||
Page(
|
Page(
|
||||||
index = i,
|
index = i,
|
||||||
imageUrl = "$apiUrl/Reader/image?chapterId=${chapterDetails.id}&page=$i&extractPdf=true&apiKey=$apiKey",
|
imageUrl = "$apiUrl/Reader/image?chapterId=${chapterDetails.id}&page=$i&extractPdf=true&apiKey=$apiKey",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}.onErrorResumeNext { error ->
|
} catch (e: Exception) {
|
||||||
Log.e(LOG_TAG, "Error fetching reading list chapter pages", error)
|
Log.e(LOG_TAG, "Error processing reading list item", e)
|
||||||
Observable.error(error)
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For normal titles
|
// For normal titles
|
||||||
return Observable.fromCallable {
|
try {
|
||||||
// Check if this is a volume URL
|
// Check if this is a volume URL
|
||||||
if (chapter.url.contains("/Volume/") || chapter.url.startsWith("volume_")) {
|
if (chapter.url.contains("/Volume/") || chapter.url.startsWith("volume_")) {
|
||||||
val volumeId = when {
|
val volumeId = when {
|
||||||
@@ -1621,7 +1590,7 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(initialPages + remainingPages).toList()
|
return@withContext (initialPages + remainingPages).toList()
|
||||||
} else {
|
} else {
|
||||||
// Original chapter handling
|
// Original chapter handling
|
||||||
val chapterId = when {
|
val chapterId = when {
|
||||||
@@ -1643,23 +1612,23 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
throw IOException("${helper.intl["error_failed_parse_chapter"]}: ${e.message}")
|
throw IOException("${helper.intl["error_failed_parse_chapter"]}: ${e.message}")
|
||||||
}
|
}
|
||||||
|
|
||||||
(0 until chapterDetails.pages).map { i ->
|
return@withContext (0 until chapterDetails.pages).map { i ->
|
||||||
Page(
|
Page(
|
||||||
index = i,
|
index = i,
|
||||||
imageUrl = "$apiUrl/Reader/image?chapterId=$chapterId&page=$i&extractPdf=true&apiKey=$apiKey",
|
imageUrl = "$apiUrl/Reader/image?chapterId=$chapterId&page=$i&extractPdf=true&apiKey=$apiKey",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.onErrorResumeNext { error ->
|
} catch (e: Exception) {
|
||||||
// Fallback to using the scanlator field if we can't get chapter details
|
// Fallback to using the scanlator field if we can't get chapter details
|
||||||
Log.e(LOG_TAG, "Error fetching chapter details, using fallback", error)
|
Log.e(LOG_TAG, "Error fetching chapter details, using fallback", e)
|
||||||
val fallbackPageCount = chapter.scanlator?.replace(" pages", "")?.toIntOrNull() ?: 1
|
val fallbackPageCount = chapter.scanlator?.replace(" pages", "")?.toIntOrNull() ?: 1
|
||||||
(0 until fallbackPageCount).map { i ->
|
return@withContext (0 until fallbackPageCount).map { i ->
|
||||||
Page(
|
Page(
|
||||||
index = i,
|
index = i,
|
||||||
imageUrl = "$apiUrl/Reader/image?chapterId=${chapter.url.substringBefore("_")}&page=$i&extractPdf=true&apiKey=$apiKey",
|
imageUrl = "$apiUrl/Reader/image?chapterId=${chapter.url.substringBefore("_")}&page=$i&extractPdf=true&apiKey=$apiKey",
|
||||||
)
|
)
|
||||||
}.let { Observable.just(it) }
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1672,7 +1641,7 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
override fun searchMangaParse(response: Response): MangasPage {
|
override fun searchMangaParse(response: Response): MangasPage {
|
||||||
return try {
|
return try {
|
||||||
val result = response.parseAs<List<SeriesDto>>()
|
val result = response.parseAs<List<SeriesDto>>()
|
||||||
val mangaList = result.map { helper.createSeriesDto(it, apiUrl, baseUrl, apiKey) }
|
val mangaList = result.map { helper.createSeriesDto(it, apiUrl, baseUrl) }
|
||||||
MangasPage(mangaList, false)
|
MangasPage(mangaList, false)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(LOG_TAG, "Error parsing search results", e)
|
Log.e(LOG_TAG, "Error parsing search results", e)
|
||||||
@@ -1701,101 +1670,77 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
private var cachedReadingLists: List<ReadingListDto> = emptyList()
|
private var cachedReadingLists: List<ReadingListDto> = emptyList()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the enabled filters if they are not empty so Mihon can show them to the user
|
* Loads the filters if they are not empty so Mihon can show them to the user
|
||||||
*/
|
*/
|
||||||
override fun getFilterList(): FilterList {
|
private fun getFilterList(
|
||||||
val toggledFilters = getToggledFilters()
|
genres: List<String>,
|
||||||
|
tags: List<String>,
|
||||||
|
ageRatings: List<String>,
|
||||||
|
collections: List<String>,
|
||||||
|
languages: List<String>,
|
||||||
|
libraries: List<String>,
|
||||||
|
people: List<String>,
|
||||||
|
pubStatus: List<String>,
|
||||||
|
smartFilters: Array<String>,
|
||||||
|
): FilterList {
|
||||||
|
return FilterList(
|
||||||
|
SortFilter(sortableList.map { it.first }.toTypedArray()),
|
||||||
|
|
||||||
val filters = try {
|
StatusSeparator(),
|
||||||
val filtersLoaded = mutableListOf<Filter<*>>()
|
StatusFilterGroup(),
|
||||||
|
|
||||||
filtersLoaded.add(SortFilter(sortableList.map { it.first }.toTypedArray()))
|
// Release year filter
|
||||||
|
|
||||||
if (smartFilters.isNotEmpty()) {
|
|
||||||
filtersLoaded.add(SmartFiltersFilter(smartFilters.map { it.name }.toTypedArray()))
|
|
||||||
}
|
|
||||||
|
|
||||||
filtersLoaded.add(SpecialListFilter())
|
|
||||||
|
|
||||||
if (toggledFilters.contains("Read Status")) {
|
|
||||||
filtersLoaded.add(StatusFilterGroup())
|
|
||||||
}
|
|
||||||
if (toggledFilters.contains("ReleaseYearRange")) {
|
|
||||||
filtersLoaded.add(
|
|
||||||
ReleaseYearRangeGroup(
|
ReleaseYearRangeGroup(
|
||||||
listOf("Min", "Max").map { ReleaseYearRange(it) },
|
listOf(
|
||||||
|
ReleaseYearRange("Min"),
|
||||||
|
ReleaseYearRange("Max"),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (genresListMeta.isNotEmpty() and toggledFilters.contains("Genres")) {
|
GenreFilterGroup(genres.map { GenreFilter(it) }),
|
||||||
filtersLoaded.add(
|
TagFilterGroup(tags.map { TagFilter(it) }),
|
||||||
GenreFilterGroup(genresListMeta.map { GenreFilter(it.title) }),
|
CollectionFilterGroup(collections.map { CollectionFilter(it) }),
|
||||||
)
|
LibrariesFilterGroup(libraries.map { LibraryFilter(it) }),
|
||||||
}
|
LanguageFilterGroup(languages.map { LanguageFilter(it) }),
|
||||||
if (tagsListMeta.isNotEmpty() and toggledFilters.contains("Tags")) {
|
PubStatusFilterGroup(pubStatus.map { PubStatusFilter(it) }),
|
||||||
filtersLoaded.add(
|
AgeRatingFilterGroup(ageRatings.map { AgeRatingFilter(it) }),
|
||||||
TagFilterGroup(tagsListMeta.map { TagFilter(it.title) }),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (ageRatingsListMeta.isNotEmpty() and toggledFilters.contains("Age Rating")) {
|
|
||||||
filtersLoaded.add(
|
|
||||||
AgeRatingFilterGroup(ageRatingsListMeta.map { AgeRatingFilter(it.title) }),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (toggledFilters.contains("Format")) {
|
|
||||||
filtersLoaded.add(
|
|
||||||
FormatsFilterGroup(
|
|
||||||
listOf(
|
|
||||||
"Image",
|
|
||||||
"Archive",
|
|
||||||
"Pdf",
|
|
||||||
"Epub",
|
|
||||||
"Unknown",
|
|
||||||
).map { FormatFilter(it) },
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (collectionsListMeta.isNotEmpty() and toggledFilters.contains("Collections")) {
|
|
||||||
filtersLoaded.add(
|
|
||||||
CollectionFilterGroup(collectionsListMeta.map { CollectionFilter(it.title) }),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (languagesListMeta.isNotEmpty() and toggledFilters.contains("Languages")) {
|
|
||||||
filtersLoaded.add(
|
|
||||||
LanguageFilterGroup(languagesListMeta.map { LanguageFilter(it.title) }),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (libraryListMeta.isNotEmpty() and toggledFilters.contains("Libraries")) {
|
|
||||||
filtersLoaded.add(
|
|
||||||
LibrariesFilterGroup(libraryListMeta.map { LibraryFilter(it.name) }),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (pubStatusListMeta.isNotEmpty() and toggledFilters.contains("Publication Status")) {
|
|
||||||
filtersLoaded.add(
|
|
||||||
PubStatusFilterGroup(
|
|
||||||
listOf(
|
|
||||||
PubStatusFilter("Ongoing"),
|
|
||||||
PubStatusFilter("Hiatus"),
|
|
||||||
PubStatusFilter("Completed"),
|
|
||||||
PubStatusFilter("Cancelled"),
|
|
||||||
PubStatusFilter("Ended"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (userRatingsListMeta.isNotEmpty() and toggledFilters.contains("Rating")) {
|
|
||||||
filtersLoaded.add(
|
|
||||||
UserRating(),
|
UserRating(),
|
||||||
|
UserRatingSeparator(),
|
||||||
|
|
||||||
|
// Special filters section
|
||||||
|
Filter.Header(helper.intl["filters_special"]),
|
||||||
|
SpecialListFilter(helper.intl["filters_special_list"]),
|
||||||
|
SmartFiltersFilter(smartFilters),
|
||||||
|
|
||||||
|
// @todo People filters (but not as filters, as a text search)
|
||||||
|
// PeopleHeaderFilter(helper.intl["filters_people"]),
|
||||||
|
// PeopleSeparatorFilter(),
|
||||||
|
// WriterPeopleFilterGroup(people.map { WriterPeopleFilter(it) }),
|
||||||
|
// PencillerPeopleFilterGroup(people.map { PencillerPeopleFilter(it) }),
|
||||||
|
// InkerPeopleFilterGroup(people.map { InkerPeopleFilter(it) }),
|
||||||
|
// ColoristPeopleFilterGroup(people.map { ColoristPeopleFilter(it) }),
|
||||||
|
// LettererPeopleFilterGroup(people.map { LettererPeopleFilter(it) }),
|
||||||
|
// CoverArtistPeopleFilterGroup(people.map { CoverArtistPeopleFilter(it) }),
|
||||||
|
// EditorPeopleFilterGroup(people.map { EditorPeopleFilter(it) }),
|
||||||
|
// PublisherPeopleFilterGroup(people.map { PublisherPeopleFilter(it) }),
|
||||||
|
// CharacterPeopleFilterGroup(people.map { CharacterPeopleFilter(it) }),
|
||||||
|
// TranslatorPeopleFilterGroup(people.map { TranslatorPeopleFilter(it) }),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
filtersLoaded
|
override fun getFilterList(): FilterList {
|
||||||
} catch (e: Exception) {
|
return getFilterList(
|
||||||
Log.e(LOG_TAG, "[FILTERS] Error while creating filter list", e)
|
genres = genresListMeta.map { it.title },
|
||||||
FilterList(emptyList())
|
tags = tagsListMeta.map { it.title },
|
||||||
}
|
ageRatings = ageRatingsListMeta.map { it.title },
|
||||||
return FilterList(filters)
|
collections = collectionsListMeta.map { it.title },
|
||||||
|
languages = languagesListMeta.map { it.title },
|
||||||
|
libraries = libraryListMeta.map { it.name },
|
||||||
|
people = peopleListMeta.map { it.name },
|
||||||
|
pubStatus = pubStatusListMeta.map { it.title },
|
||||||
|
smartFilters = smartFilters.map { it.name }.toTypedArray(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
class LoginErrorException(message: String? = null, cause: Throwable? = null) : Exception(message, cause) {
|
class LoginErrorException(message: String? = null, cause: Throwable? = null) : Exception(message, cause) {
|
||||||
@@ -2179,165 +2124,88 @@ class Kavita(private val suffix: String = "") : ConfigurableSource, UnmeteredSou
|
|||||||
Log.v(LOG_TAG, "[Login] Login successful")
|
Log.v(LOG_TAG, "[Login] Login successful")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (apiUrl.isNotBlank()) {
|
if (apiUrl.isNotBlank()) {
|
||||||
Single.fromCallable {
|
scope.launch {
|
||||||
doLogin()
|
|
||||||
try { // Get current version
|
|
||||||
val requestUrl = "$apiUrl/Server/server-info-slim"
|
|
||||||
val serverInfoDto = client.newCall(GET(requestUrl, headersBuilder().build()))
|
|
||||||
.execute()
|
|
||||||
.parseAs<ServerInfoDto>()
|
|
||||||
Log.e(
|
|
||||||
LOG_TAG,
|
|
||||||
"Extension version: code=${AppInfo.getVersionCode()} name=${AppInfo.getVersionName()}" +
|
|
||||||
" - - Kavita version: ${serverInfoDto.kavitaVersion} - - Lang:${Locale.getDefault()}",
|
|
||||||
) // this is not a real error. Using this so it gets printed in dump logs if there's any error
|
|
||||||
} catch (e: EmptyRequestBody) {
|
|
||||||
Log.e(LOG_TAG, "Extension version: code=${AppInfo.getVersionCode()} - name=${AppInfo.getVersionName()}")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(LOG_TAG, "Tachiyomi version: code=${AppInfo.getVersionCode()} - name=${AppInfo.getVersionName()}", e)
|
|
||||||
}
|
|
||||||
try { // Load Filters
|
|
||||||
// Genres
|
|
||||||
Log.v(LOG_TAG, "[Filter] Fetching filters ")
|
|
||||||
client.newCall(GET("$apiUrl/Metadata/genres", headersBuilder().build()))
|
|
||||||
.execute().use { response ->
|
|
||||||
|
|
||||||
genresListMeta = try {
|
|
||||||
response.body.use { json.decodeFromString(it.string()) }
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(LOG_TAG, "[Filter] Error decoding JSON for genres filter -> ${response.body}", e)
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// tagsListMeta
|
|
||||||
client.newCall(GET("$apiUrl/Metadata/tags", headersBuilder().build()))
|
|
||||||
.execute().use { response ->
|
|
||||||
tagsListMeta = try {
|
|
||||||
response.body.use { json.decodeFromString(it.string()) }
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(LOG_TAG, "[Filter] Error decoding JSON for tagsList filter", e)
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// age-ratings
|
|
||||||
client.newCall(GET("$apiUrl/Metadata/age-ratings", headersBuilder().build()))
|
|
||||||
.execute().use { response ->
|
|
||||||
ageRatingsListMeta = try {
|
|
||||||
response.body.use { json.decodeFromString(it.string()) }
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(
|
|
||||||
LOG_TAG,
|
|
||||||
"[Filter] Error decoding JSON for age-ratings filter",
|
|
||||||
e,
|
|
||||||
)
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// collectionsListMeta
|
|
||||||
client.newCall(GET("$apiUrl/Collection", headersBuilder().build()))
|
|
||||||
.execute().use { response ->
|
|
||||||
collectionsListMeta = try {
|
|
||||||
response.body.use { json.decodeFromString(it.string()) }
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(
|
|
||||||
LOG_TAG,
|
|
||||||
"[Filter] Error decoding JSON for collectionsListMeta filter",
|
|
||||||
e,
|
|
||||||
)
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// languagesListMeta
|
|
||||||
client.newCall(GET("$apiUrl/Metadata/languages", headersBuilder().build()))
|
|
||||||
.execute().use { response ->
|
|
||||||
languagesListMeta = try {
|
|
||||||
response.body.use { json.decodeFromString(it.string()) }
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(
|
|
||||||
LOG_TAG,
|
|
||||||
"[Filter] Error decoding JSON for languagesListMeta filter",
|
|
||||||
e,
|
|
||||||
)
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// libraries
|
|
||||||
client.newCall(GET("$apiUrl/Library/libraries", headersBuilder().build()))
|
|
||||||
.execute().use { response ->
|
|
||||||
libraryListMeta = try {
|
|
||||||
response.body.use { json.decodeFromString(it.string()) }
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(
|
|
||||||
LOG_TAG,
|
|
||||||
"[Filter] Error decoding JSON for libraries filter",
|
|
||||||
e,
|
|
||||||
)
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// peopleListMeta
|
|
||||||
client.newCall(GET("$apiUrl/Metadata/people", headersBuilder().build()))
|
|
||||||
.execute().use { response ->
|
|
||||||
peopleListMeta = try {
|
|
||||||
val jsonString = response.body.string()
|
|
||||||
// Log.d(LOG_TAG, "Raw people metadata: $jsonString") // Debug log
|
|
||||||
json.decodeFromString<List<MetadataPeople>>(jsonString)
|
|
||||||
.filter { it.role != null } // Filter out entries without role
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(LOG_TAG, "Error decoding people metadata", e)
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.newCall(GET("$apiUrl/Metadata/publication-status", headersBuilder().build()))
|
|
||||||
.execute().use { response ->
|
|
||||||
if (!response.isSuccessful) {
|
|
||||||
Log.e(LOG_TAG, "Failed to fetch publication status: ${response.code}")
|
|
||||||
pubStatusListMeta = emptyList()
|
|
||||||
return@use
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
pubStatusListMeta = response.body.use { json.decodeFromString(it.string()) }
|
doLogin()
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(LOG_TAG, "Error decoding publication status JSON", e)
|
// Load all filters in parallel
|
||||||
pubStatusListMeta = emptyList()
|
val deferredFilters = listOf(
|
||||||
}
|
async {
|
||||||
}
|
genresListMeta = loadMetadata("$apiUrl/Metadata/genres")
|
||||||
client.newCall(GET("$apiUrl/filter", headersBuilder().build()))
|
Log.d(LOG_TAG, "Loaded ${genresListMeta.size} genres")
|
||||||
.execute().use { response ->
|
},
|
||||||
smartFilters = try {
|
async {
|
||||||
response.body.use { json.decodeFromString(it.string()) }
|
tagsListMeta = loadMetadata("$apiUrl/Metadata/tags")
|
||||||
} catch (e: Exception) {
|
Log.d(LOG_TAG, "Loaded ${tagsListMeta.size} tags")
|
||||||
Log.e(
|
},
|
||||||
LOG_TAG,
|
async {
|
||||||
"error while decoding JSON for smartfilters",
|
ageRatingsListMeta = loadMetadata("$apiUrl/Metadata/age-ratings")
|
||||||
e,
|
Log.d(LOG_TAG, "Loaded ${ageRatingsListMeta.size} age ratings")
|
||||||
)
|
},
|
||||||
emptyList()
|
async {
|
||||||
}
|
collectionsListMeta = loadMetadata("$apiUrl/Collection")
|
||||||
}
|
Log.d(LOG_TAG, "Loaded ${collectionsListMeta.size} collections")
|
||||||
} catch (e: Exception) {
|
},
|
||||||
throw LoadingFilterFailed("Failed Loading Filters", e.cause)
|
async {
|
||||||
}
|
languagesListMeta = loadMetadata("$apiUrl/Metadata/languages")
|
||||||
}
|
Log.d(LOG_TAG, "Loaded ${languagesListMeta.size} languages")
|
||||||
.subscribeOn(Schedulers.io())
|
},
|
||||||
.observeOn(Schedulers.io())
|
async {
|
||||||
.subscribe(
|
libraryListMeta = loadMetadata("$apiUrl/Library/libraries")
|
||||||
{},
|
Log.d(LOG_TAG, "Loaded ${libraryListMeta.size} libraries")
|
||||||
{ tr ->
|
},
|
||||||
// Avoid polluting logs with traces of exception
|
async {
|
||||||
if (tr is EmptyRequestBody || tr is LoginErrorException) {
|
peopleListMeta = loadMetadata("$apiUrl/Metadata/people")
|
||||||
Log.e(LOG_TAG, "error while doing initial calls\n${tr.cause}")
|
Log.d(LOG_TAG, "Loaded ${peopleListMeta.size} people")
|
||||||
return@subscribe
|
},
|
||||||
}
|
async {
|
||||||
if (tr is ConnectException) { // avoid polluting logs with traces of exception
|
pubStatusListMeta = loadMetadata("$apiUrl/Metadata/publication-status")
|
||||||
Log.e(LOG_TAG, "Error while doing initial calls\n${tr.cause}")
|
Log.d(LOG_TAG, "Loaded ${pubStatusListMeta.size} publication statuses")
|
||||||
return@subscribe
|
},
|
||||||
}
|
async {
|
||||||
Log.e(LOG_TAG, "error while doing initial calls", tr)
|
smartFilters = loadMetadata("$apiUrl/filter")
|
||||||
|
Log.d(LOG_TAG, "Loaded ${smartFilters.size} smart filters")
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
deferredFilters.awaitAll()
|
||||||
|
|
||||||
|
// Get server info
|
||||||
|
val serverInfoDto = client.newCall(GET("$apiUrl/Server/server-info-slim", headersBuilder().build()))
|
||||||
|
.execute()
|
||||||
|
.parseAs<ServerInfoDto>()
|
||||||
|
Log.d(LOG_TAG, "Kavita version: ${serverInfoDto.kavitaVersion}")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(LOG_TAG, "Initialization error", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add cleanup for the coroutine scope
|
||||||
|
fun cleanup() {
|
||||||
|
scope.cancel()
|
||||||
|
// Clear any cached data if needed
|
||||||
|
genresListMeta = emptyList()
|
||||||
|
tagsListMeta = emptyList()
|
||||||
|
ageRatingsListMeta = emptyList()
|
||||||
|
peopleListMeta = emptyList()
|
||||||
|
pubStatusListMeta = emptyList()
|
||||||
|
languagesListMeta = emptyList()
|
||||||
|
libraryListMeta = emptyList()
|
||||||
|
collectionsListMeta = emptyList()
|
||||||
|
smartFilters = emptyList()
|
||||||
|
cachedReadingLists = emptyList()
|
||||||
|
Log.d(LOG_TAG, "Cleanup completed")
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <reified T> loadMetadata(url: String): T {
|
||||||
|
return client.newCall(GET(url, headersBuilder().build()))
|
||||||
|
.execute()
|
||||||
|
.parseAs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -47,18 +47,17 @@ class KavitaHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rating Providers from Series-Details-Plus
|
// @todo Rating Providers from Series-Details-Plus
|
||||||
private fun getProviderName(provider: Int): String = when (provider) {
|
// private fun getProviderName(provider: Int): String = when (provider) {
|
||||||
0 -> "User"
|
// 0 -> "User"
|
||||||
1 -> "AniList"
|
// 1 -> "AniList"
|
||||||
2 -> "MyAnimeList"
|
// 2 -> "MyAnimeList"
|
||||||
3 -> "MangaUpdates"
|
// 3 -> "MangaUpdates"
|
||||||
else -> "Rating"
|
// else -> "Rating"
|
||||||
}
|
// }
|
||||||
|
|
||||||
fun createSeriesDto(obj: SeriesDto, baseUrl: String, apiUrl: String, apiKey: String): SManga =
|
fun createSeriesDto(obj: SeriesDto, baseUrl: String, apiKey: String): SManga =
|
||||||
SManga.create().apply {
|
SManga.create().apply {
|
||||||
// url = "$baseUrl/library/${obj.libraryId}/series/${obj.id}"
|
|
||||||
url = "$baseUrl/Series/${obj.id}"
|
url = "$baseUrl/Series/${obj.id}"
|
||||||
title = obj.name
|
title = obj.name
|
||||||
thumbnail_url = "$baseUrl/image/series-cover?seriesId=${obj.id}&apiKey=$apiKey"
|
thumbnail_url = "$baseUrl/image/series-cover?seriesId=${obj.id}&apiKey=$apiKey"
|
||||||
@@ -102,16 +101,24 @@ class KavitaHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ChapterType.SingleFileVolume -> {
|
ChapterType.SingleFileVolume -> {
|
||||||
cleanChapterTitle(
|
val volumeNumber = formatVolumeNumber(volume)
|
||||||
|
val cleanVolumeName = volume.name.trim()
|
||||||
|
|
||||||
|
// Always use the API's volume number, never parse from title
|
||||||
when {
|
when {
|
||||||
volume.name.any { it.isLetter() } -> "v${formatVolumeNumber(volume)} - ${volume.name}"
|
// If name is empty or just numbers (Kavita default)
|
||||||
else -> "Volume ${formatVolumeNumber(volume)}"
|
cleanVolumeName.isEmpty() || cleanVolumeName.none { it.isLetter() } -> {
|
||||||
},
|
cleanChapterTitle("Volume $volumeNumber")
|
||||||
ChapterTitleContext(
|
}
|
||||||
mangaTitle = mangaTitle,
|
// If name already contains volume info (Vol. 3, V3, etc.)
|
||||||
volumeName = volume.name,
|
cleanVolumeName.contains(Regex("(?i)(vol|volume|v)[.\\s]*\\d+")) -> {
|
||||||
),
|
cleanChapterTitle(cleanVolumeName)
|
||||||
)
|
}
|
||||||
|
// Normal case - prefix with volume number
|
||||||
|
else -> {
|
||||||
|
cleanChapterTitle("$volumeNumber - $cleanVolumeName")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ChapterType.Special -> {
|
ChapterType.Special -> {
|
||||||
@@ -163,47 +170,6 @@ class KavitaHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo Keeping this in case we're switching the logic here this after the Mihon PR
|
|
||||||
// chapter_number = when {
|
|
||||||
// type == ChapterType.SingleFileVolume && singleFileVolumeNumber != null ->
|
|
||||||
// singleFileVolumeNumber.toFloat()
|
|
||||||
//
|
|
||||||
// type == ChapterType.SingleFileVolume -> {
|
|
||||||
// // For standalone volumes, use positive numbers
|
|
||||||
// volume.minNumber.toFloat()
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // For regular chapters
|
|
||||||
// type != ChapterType.SingleFileVolume && type != ChapterType.Special -> {
|
|
||||||
// // Handle decimal chapter numbers
|
|
||||||
// if (chapter.minNumber % 1 != 0.0) {
|
|
||||||
// chapter.minNumber.toFloat()
|
|
||||||
// } else {
|
|
||||||
// chapter.minNumber.toInt().toFloat()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // For volumes/specials in mixed content, place them below chapter 0
|
|
||||||
// else -> {
|
|
||||||
// val volumeNum = try {
|
|
||||||
// if (volume.minNumber % 1 != 0.0) {
|
|
||||||
// volume.minNumber.toFloat()
|
|
||||||
// } else {
|
|
||||||
// volume.minNumber.toInt().toFloat()
|
|
||||||
// }
|
|
||||||
// } catch (e: NumberFormatException) {
|
|
||||||
// 0f
|
|
||||||
// }
|
|
||||||
// -volumeNum - KavitaConstants.VOLUME_NUMBER_OFFSET
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// url = when {
|
|
||||||
// type == ChapterType.SingleFileVolume && singleFileVolumeNumber != null ->
|
|
||||||
// "volume_${volume.id}"
|
|
||||||
// else -> "chapter_${chapter.id}"
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Handle decimal chapter numbers properly
|
// Handle decimal chapter numbers properly
|
||||||
chapter_number = when {
|
chapter_number = when {
|
||||||
type == ChapterType.SingleFileVolume && singleFileVolumeNumber != null ->
|
type == ChapterType.SingleFileVolume && singleFileVolumeNumber != null ->
|
||||||
|
|||||||
Reference in New Issue
Block a user