Mihon

repository·main·Indexed 10 days ago

https://github.com/mihonapp/mihon

An open-source, full-featured manga and comic reader for Android. It supports local content, multiple reading modes, and integration with media trackers such as MyAnimeList, AniList, Kitsu, and MangaUpdates. The application includes a configurable reader engine, library management with automatic updates, and cloud backup synchronization.

Tokens
6.9K
Snippets
19
Records
34
Agent score
97%

What's inside Mihon

  1. Overview of Mihon App

    main

    Mihon is a full-featured reader for Android designed to discover and read manga, webtoons, comics, and more. It provides local reading capabilities, a highly configurable reader engine, and integration with various tracking services.

    Key Features

    • Local Content Reading: Read your downloaded content directly on your device.
    • Configurable Reader: Multiple viewers, reading directions, and various display settings.
    • Tracker Support: Integration with services like MangaBaka, MyAnimeList, AniList, Kitsu, MangaUpdates, Shikimori, Bangumi, and Hikka.
    • Library Management: Organize content using categories and schedule automatic updates for new chapters.
    • Backups: Create local backups or sync to your preferred cloud service for offline reading or data portability.
    • Customization: Support for light and dark themes.
  2. Understand the Mihon download directory structure

    main

    Mihon organizes downloaded content using a hierarchical path scheme to ensure files are logically grouped by source and title. The structure follows this pattern:

    /<root downloads dir>/<source name>/<manga>/<chapter>

    • Root downloads dir: The base directory configured in the app's storage settings.
    • Source name: A sanitized version of the source name (e.g., the name of the website).
    • Manga: A sanitized version of the manga's title.
    • Chapter: A unique directory name generated from the chapter name, scanlator, and a partial MD5 hash of the chapter URL to prevent collisions and handle name changes.
  3. Manage Incognito Mode notifications

    main

    When incognitoMode is enabled in BasePreferences, the application registers a DisableIncognitoReceiver and displays a persistent notification. This notification allows the user to disable Incognito Mode directly from the notification shade via a PendingIntent targeting the ACTION_DISABLE_INCOGNITO_MODE action.

    • Action: tachi.action.DISABLE_INCOGNITO_MODE
    • Notification ID: Notifications.ID_INCOGNITO_MODE
    • Channel: Notifications.CHANNEL_INCOGNITO_MODE
  4. Use LibraryUpdateNotifier to manage library update notifications

    main

    LibraryUpdateNotifier is a class used to handle various types of Android notifications related to library updates, such as progress tracking, error reporting, and new chapter alerts. It respects user privacy settings via securityPreferences.hideNotificationContent.

    Key functionalities include:

    • Progress Tracking: Showing a persistent notification with a progress bar while updating manga.
    • Error Reporting: Notifying the user when updates fail and providing a link to an error log.
    • New Chapter Alerts: Grouping notifications for new chapters and providing per-manga notifications with actions like 'Mark as read' or 'Download'.
    • Warning Notifications: Warning users when a single source is being checked excessively (bulk updates).
    val notifier = LibraryUpdateNotifier(context)
    
    // Show progress
    notifier.showProgressNotification(mangaList, current, total)
    
    // Show errors
    notifier.showUpdateErrorNotification(failedCount, errorLogUri)
    
    // Show new chapter updates
    notifier.showUpdateNotifications(updatesList)
  5. Handle WebView package name spoofing

    main

    The App class overrides getPackageName() to provide a spoofed package name to Chromium-based WebView requests. This is done by inspecting the current thread's stack trace to detect if the call originates from Chromium's internal package info logic. If a Chromium call is detected, WebViewUtil.spoofedPackageName(applicationContext) is returned instead of the actual application package name.

    override fun getPackageName(): String {
        try {
            val stackTrace = Thread.currentThread().stackTrace
            val isChromiumCall = stackTrace.any { trace ->
                trace.className.lowercase() in setOf("org.chromium.base.buildinfo", "org.chromium.base.apkinfo") &&
                    trace.methodName.lowercase() in setOf("getall", "getpackagename", "<init>")
            }
    
            if (isChromiumCall) return WebViewUtil.spoofedPackageName(applicationContext)
        } catch (_: Exception) {
        }
    
        return super.getPackageName()
    }
  6. Generate chapter directory names

    main

    Mihon generates chapter directory names using a specific formula to ensure uniqueness and compatibility. The name is constructed as follows:

    1. Sanitized Name: The chapter name (defaults to "Chapter" if blank).
    2. Scanlator Prefix: If a scanlator is present, it is prepended with an underscore (e.g., ScanlatorName_ChapterName).
    3. Length Constraint: The name is truncated using DiskUtil.buildValidFilename to fit within DiskUtil.MAX_FILE_NAME_BYTES - 11.
    4. URL Hash: A 6-character MD5 hash of the chapter URL is appended with an underscore (e.g., _abc123).

    Example format: Scanlator_ChapterName_abc123

  7. Handle deferred chapter deletion

    main

    To prevent accidental data loss, DownloadManager supports a mechanism to enqueue chapters for deletion at a later time rather than deleting them immediately.

    • enqueueChaptersToDelete(chapters: List<Chapter>, manga: Manga): Adds a list of chapters to a pending deletion queue. This is a suspend function.
    • deletePendingChapters(): Triggers the actual deletion of all chapters currently held in the pendingDeleter queue. This should be called when the user explicitly triggers a cleanup task.
  8. Manage active downloads with DownloadStore

    main

    The DownloadStore class is responsible for persisting active downloads across application restarts using Android's SharedPreferences. It allows you to save, remove, and restore a queue of Download objects.

    Important Note: The restore() method performs blocking operations (via runBlocking) to fetch manga and chapter data from the database. It must be called in a background thread to avoid blocking the main UI thread.

  9. Enqueue and control chapter downloads

    main

    Use these methods to manage the lifecycle of chapter downloads in the queue.

    • downloadChapters(manga: Manga, chapters: List<Chapter>, autoStart: Boolean = true): Enqueues a list of chapters for a specific manga. If autoStart is true, the downloader begins processing the queue immediately.
    • startDownloadNow(chapterId: Long): Immediately moves a specific chapter to the front of the queue and starts the downloader.
    • addDownloadsToStartOfQueue(downloads: List<Download>): Prepends a list of Download objects to the start of the queue.
    • pauseDownloads(): Pauses the current downloader and stops the active job.
    • clearQueue(): Empties the entire download queue and stops the downloader.
    • reorderQueue(downloads: List<Download>): Manually updates the order of the download queue.
  10. Manage chapter and image data with ChapterCache

    main

    The ChapterCache class provides a mechanism for caching both chapter metadata (page lists) and individual images to disk using an LRU (Least Recently Used) strategy. It uses a DiskLruCache stored in the application's cache directory under chapter_disk_cache.

    Key Capabilities:

    • Chapter Metadata: Stores and retrieves a list of Page objects for a given Chapter by serializing them to JSON.
    • Image Caching: Stores image files derived from URLs and provides methods to check for existence or retrieve the local File object.
    • Cache Maintenance: Provides a clear() method to wipe the cache contents.

    Implementation Details:

    • Storage Format: Files are stored using an MD5 hash of their keys (either the chapter's manga ID + URL or the image URL) with a .0 extension.
    • Cache Size: The cache is limited to 100 MB (100L * 1024 * 1024 bytes).
    val cache = ChapterCache(context, json)
    
    // Caching chapter pages
    val pages: List<Page> = cache.getPageListFromCache(chapter)
    cache.putPageListToCache(chapter, pages)
    
    // Caching an image from a network response
    cache.putImageToCache(imageUrl, response)
    
    // Checking image status
    if (cache.isImageInCache(imageUrl)) {
        val file = cache.getImageFile(imageUrl)
    }
  11. Manage download state with DownloadCache

    main

    The DownloadCache class provides a high-performance way to query the state of downloaded content (manga, chapters, and sources) without performing expensive filesystem operations every time. It maintains an in-memory representation of the downloads directory and periodically synchronizes with the disk.

    Key Features

    • Fast Queries: Check if a chapter is downloaded using isChapterDownloaded.
    • Download Statistics: Retrieve total download counts or counts for a specific Manga using getTotalDownloadCount and getDownloadCount.
    • Reactive Updates: Observe changes to the download state via the changes Flow.
    • Cache Invalidation: The cache is automatically invalidated and renewed every hour or when StorageManager signals changes.

    Observing Changes

    You can collect from the changes Flow to react whenever the cache is updated (e.g., after adding or removing chapters/manga).

    downloadCache.changes.collect { 
        // The cache has changed, refresh your UI
    }